| // Copyright 2018 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/externally_managed_app_manager.h" |
| |
| #include <algorithm> |
| #include <map> |
| #include <memory> |
| #include <ostream> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/check_is_test.h" |
| #include "base/containers/contains.h" |
| #include "base/feature_list.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/not_fatal_until.h" |
| #include "base/stl_util.h" |
| #include "base/strings/to_string.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/values.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/web_applications/external_install_options.h" |
| #include "chrome/browser/web_applications/externally_managed_app_registration_task.h" |
| #include "chrome/browser/web_applications/locks/all_apps_lock.h" |
| #include "chrome/browser/web_applications/proto/web_app_install_state.pb.h" |
| #include "chrome/browser/web_applications/web_app_command_scheduler.h" |
| #include "chrome/browser/web_applications/web_app_install_utils.h" |
| #include "chrome/browser/web_applications/web_app_management_type.h" |
| #include "chrome/browser/web_applications/web_app_provider.h" |
| #include "chrome/browser/web_applications/web_app_registrar.h" |
| #include "chrome/browser/web_applications/web_app_registry_update.h" |
| #include "chrome/browser/web_applications/web_app_sync_bridge.h" |
| #include "chrome/browser/web_applications/web_app_ui_manager.h" |
| #include "chrome/browser/web_applications/web_contents/web_app_data_retriever.h" |
| #include "chrome/browser/web_applications/web_contents/web_contents_manager.h" |
| #include "chrome/common/chrome_features.h" |
| #include "components/webapps/browser/install_result_code.h" |
| #include "components/webapps/browser/uninstall_result_code.h" |
| #include "components/webapps/browser/web_contents/web_app_url_loader.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/url_constants.h" |
| |
| namespace web_app { |
| |
| namespace { |
| |
| // TODO(crbug.com/408163317): Do not use, this is an implementation detail and |
| // will be removed later. |
| bool& DropRequestsForTesting() { |
| static bool drop_requests_for_testing_ = false; |
| return drop_requests_for_testing_; |
| } |
| |
| } // namespace |
| |
| ExternallyManagedAppManagerInstallResult:: |
| ExternallyManagedAppManagerInstallResult() = default; |
| |
| ExternallyManagedAppManagerInstallResult:: |
| ExternallyManagedAppManagerInstallResult( |
| webapps::InstallResultCode code, |
| std::optional<webapps::AppId> app_id, |
| bool did_uninstall_and_replace) |
| : code(code), |
| app_id(std::move(app_id)), |
| did_uninstall_and_replace(did_uninstall_and_replace) {} |
| |
| ExternallyManagedAppManagerInstallResult:: |
| ExternallyManagedAppManagerInstallResult( |
| const ExternallyManagedAppManagerInstallResult&) = default; |
| |
| ExternallyManagedAppManagerInstallResult:: |
| ~ExternallyManagedAppManagerInstallResult() = default; |
| |
| bool ExternallyManagedAppManagerInstallResult::operator==( |
| const ExternallyManagedAppManagerInstallResult& other) const { |
| return std::tie(code, app_id, did_uninstall_and_replace) == |
| std::tie(other.code, other.app_id, other.did_uninstall_and_replace); |
| } |
| |
| ExternallyManagedAppManager::ScopedDropRequestsForTesting:: |
| ScopedDropRequestsForTesting() { |
| CHECK_IS_TEST(); |
| DropRequestsForTesting() = true; // IN-TEST |
| } |
| |
| ExternallyManagedAppManager::ScopedDropRequestsForTesting:: |
| ~ScopedDropRequestsForTesting() { |
| DropRequestsForTesting() = false; // IN-TEST |
| } |
| |
| ExternallyManagedAppManager::SynchronizeRequest::SynchronizeRequest( |
| SynchronizeCallback callback, |
| std::vector<ExternalInstallOptions> pending_installs, |
| int remaining_uninstall_requests) |
| : callback(std::move(callback)), |
| remaining_install_requests(pending_installs.size()), |
| pending_installs(std::move(pending_installs)), |
| remaining_uninstall_requests(remaining_uninstall_requests) { |
| CHECK(this->callback); |
| } |
| |
| ExternallyManagedAppManager::SynchronizeRequest::~SynchronizeRequest() = |
| default; |
| |
| ExternallyManagedAppManager::SynchronizeRequest& |
| ExternallyManagedAppManager::SynchronizeRequest::operator=( |
| ExternallyManagedAppManager::SynchronizeRequest&&) = default; |
| |
| ExternallyManagedAppManager::SynchronizeRequest::SynchronizeRequest( |
| SynchronizeRequest&& other) = default; |
| |
| struct ExternallyManagedAppManager::ExternalInstallMetadata { |
| ExternalInstallMetadata(ExternalInstallOptions options, |
| OnceInstallCallback callback) |
| : options(std::move(options)), callback(std::move(callback)) {} |
| ~ExternalInstallMetadata() = default; |
| |
| ExternalInstallOptions options; |
| OnceInstallCallback callback; |
| }; |
| |
| ExternallyManagedAppManager::ExternallyManagedAppManager(Profile* profile) |
| : profile_(profile) {} |
| |
| ExternallyManagedAppManager::~ExternallyManagedAppManager() { |
| DCHECK(!registration_callback_); |
| // Extra check to verify that web_contents is released even if |
| // shutdown somehow has not been invoked. |
| if (!IsShuttingDown()) { |
| Shutdown(); |
| } |
| } |
| |
| void ExternallyManagedAppManager::SetProvider(base::PassKey<WebAppProvider>, |
| WebAppProvider& provider) { |
| provider_ = &provider; |
| // TODO(http://b/283521737): Remove this and use WebContentsManager. |
| url_loader_ = provider.web_contents_manager().CreateUrlLoader(); |
| // TODO(http://b/283521737): Remove this and use WebContentsManager. |
| data_retriever_factory_ = base::BindRepeating( |
| [](base::WeakPtr<WebContentsManager> web_contents_manager) |
| -> std::unique_ptr<WebAppDataRetriever> { |
| if (!web_contents_manager) { |
| return nullptr; |
| } |
| return web_contents_manager->CreateDataRetriever(); |
| }, |
| provider.web_contents_manager().GetWeakPtr()); |
| } |
| |
| void ExternallyManagedAppManager::InstallNow( |
| ExternalInstallOptions install_options, |
| OnceInstallCallback callback) { |
| pending_installs_metadata_.push_front( |
| std::make_unique<ExternalInstallMetadata>(std::move(install_options), |
| std::move(callback))); |
| |
| PostMaybeStartNext(); |
| } |
| |
| void ExternallyManagedAppManager::Install( |
| ExternalInstallOptions install_options, |
| OnceInstallCallback callback) { |
| pending_installs_metadata_.push_back( |
| std::make_unique<ExternalInstallMetadata>(std::move(install_options), |
| std::move(callback))); |
| |
| PostMaybeStartNext(); |
| } |
| |
| void ExternallyManagedAppManager::SynchronizeInstalledApps( |
| std::vector<ExternalInstallOptions> desired_apps_install_options, |
| ExternalInstallSource install_source, |
| SynchronizeCallback callback) { |
| CHECK(callback); |
| CHECK(std::ranges::all_of( |
| desired_apps_install_options, |
| [&install_source](const ExternalInstallOptions& install_options) { |
| return install_options.install_source == install_source; |
| })); |
| // Only one concurrent SynchronizeInstalledApps() expected per |
| // ExternalInstallSource. |
| CHECK(!base::Contains(synchronize_requests_, install_source)); |
| provider_->scheduler().ScheduleCallback<AllAppsLock>( |
| "ExternallyManagedAppManager::SynchronizeInstalledApps", |
| AllAppsLockDescription(), |
| base::BindOnce( |
| &ExternallyManagedAppManager::SynchronizeInstalledAppsOnLockAcquired, |
| weak_ptr_factory_.GetWeakPtr(), |
| std::move(desired_apps_install_options), install_source, |
| std::move(callback)), |
| /*on_complete=*/base::DoNothing()); |
| } |
| |
| void ExternallyManagedAppManager::Shutdown() { |
| is_in_shutdown_ = true; |
| pending_registrations_.clear(); |
| current_registration_.reset(); |
| pending_installs_metadata_.clear(); |
| url_loader_.reset(); |
| // `current_install_` keeps a pointer to `web_contents_` so destroy it before |
| // releasing the WebContents. |
| current_install_metadata_.reset(); |
| ReleaseWebContents(); |
| } |
| |
| void ExternallyManagedAppManager::SetUrlLoaderForTesting( |
| std::unique_ptr<webapps::WebAppUrlLoader> url_loader) { |
| CHECK_IS_TEST(); |
| url_loader_ = std::move(url_loader); |
| } |
| |
| void ExternallyManagedAppManager::SetDataRetrieverFactoryForTesting( |
| base::RepeatingCallback<std::unique_ptr<WebAppDataRetriever>()> factory) { |
| CHECK_IS_TEST(); |
| data_retriever_factory_ = std::move(factory); |
| } |
| |
| void ExternallyManagedAppManager::ReleaseWebContents() { |
| DCHECK(pending_registrations_.empty()); |
| DCHECK(!current_registration_); |
| DCHECK(pending_installs_metadata_.empty()); |
| DCHECK(!current_install_metadata_); |
| |
| web_contents_.reset(); |
| } |
| |
| std::unique_ptr<ExternallyManagedAppRegistrationTaskBase> |
| ExternallyManagedAppManager::CreateRegistration( |
| GURL install_url, |
| const base::TimeDelta registration_timeout) { |
| DCHECK(!IsShuttingDown()); |
| ExternallyManagedAppRegistrationTask::RegistrationCallback callback = |
| base::BindOnce(&ExternallyManagedAppManager::OnRegistrationFinished, |
| weak_ptr_factory_.GetWeakPtr(), install_url); |
| return std::make_unique<ExternallyManagedAppRegistrationTask>( |
| std::move(install_url), registration_timeout, url_loader_.get(), |
| web_contents_.get(), std::move(callback)); |
| } |
| |
| void ExternallyManagedAppManager::OnRegistrationFinished( |
| const GURL& install_url, |
| RegistrationResultCode result) { |
| if (IsShuttingDown()) { |
| return; |
| } |
| |
| DCHECK(!current_registration_ || |
| current_registration_->install_url() == install_url); |
| |
| if (registration_callback_) { |
| registration_callback_.Run(install_url, result); |
| } |
| |
| current_registration_.reset(); |
| PostMaybeStartNext(); |
| } |
| |
| void ExternallyManagedAppManager::PostMaybeStartNext() { |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&ExternallyManagedAppManager::MaybeStartNext, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void ExternallyManagedAppManager::MaybeStartNext() { |
| if (current_install_metadata_ || IsShuttingDown()) { |
| return; |
| } |
| provider_->scheduler().ScheduleCallback( |
| "ExternallyManagedAppManager::MaybeStartNext", AllAppsLockDescription(), |
| base::BindOnce(&ExternallyManagedAppManager::MaybeStartNextOnLockAcquired, |
| weak_ptr_factory_.GetWeakPtr()), |
| /*on_complete=*/base::DoNothing()); |
| } |
| |
| void ExternallyManagedAppManager::MaybeStartNextOnLockAcquired( |
| AllAppsLock& lock, |
| base::Value::Dict& debug_value) { |
| if (current_install_metadata_ || IsShuttingDown()) { |
| return; |
| } |
| |
| while (!pending_installs_metadata_.empty()) { |
| std::unique_ptr<ExternalInstallMetadata> front = |
| std::move(pending_installs_metadata_.front()); |
| pending_installs_metadata_.pop_front(); |
| |
| const ExternalInstallOptions& install_options = front->options; |
| |
| CHECK(install_options.install_url.is_valid(), base::NotFatalUntil::M130); |
| std::optional<webapps::AppId> app_id = |
| lock.registrar().LookupExternalAppId(install_options.install_url); |
| debug_value.Set("app_id_from_install_url", app_id.value_or("<none>")); |
| |
| const bool is_placeholder_installed = |
| app_id.has_value() |
| ? lock.registrar().IsPlaceholderApp( |
| app_id.value(), ConvertExternalInstallSourceToSource( |
| install_options.install_source)) |
| : false; |
| debug_value.Set("is_placeholder_installed", is_placeholder_installed); |
| |
| if (install_options.force_reinstall) { |
| StartInstallationTask( |
| std::move(front), |
| /*installed_placeholder_app_id=*/is_placeholder_installed |
| ? std::move(app_id) |
| : std::nullopt); |
| return; |
| } |
| |
| // If the URL is not in web_app registrar, |
| // then no external source has installed it. |
| if (!app_id.has_value()) { |
| StartInstallationTask(std::move(front), |
| /*installed_placeholder_app_id=*/std::nullopt); |
| return; |
| } |
| |
| std::optional<proto::InstallState> install_state = |
| lock.registrar().GetInstallState(app_id.value()); |
| if (install_state == web_app::proto::INSTALLED_WITH_OS_INTEGRATION || |
| install_state == web_app::proto::INSTALLED_WITHOUT_OS_INTEGRATION) { |
| if (install_options.placeholder_resolution_behavior == |
| PlaceholderResolutionBehavior::kWaitForAppWindowsClosed && |
| lock.ui_manager().GetNumWindowsForApp(app_id.value()) != 0) { |
| debug_value.Set("waiting_for_windows_closed", true); |
| lock.ui_manager().NotifyOnAllAppWindowsClosed( |
| app_id.value(), |
| base::BindOnce(&ExternallyManagedAppManager::Install, |
| weak_ptr_factory_.GetWeakPtr(), install_options, |
| std::move(front->callback))); |
| continue; |
| } |
| |
| // If the app is already installed, only reinstall it if the app is a |
| // placeholder app and the client asked for it to be reinstalled. |
| if (is_placeholder_installed) { |
| debug_value.Set("retrying_install_because_placeholder", true); |
| StartInstallationTask(std::move(front), std::move(app_id)); |
| return; |
| } |
| |
| // TODO(crbug.com/40216215): Investigate re-install of the app for all |
| // WebAppManagement sources. |
| if ((ConvertExternalInstallSourceToSource( |
| install_options.install_source) == WebAppManagement::kPolicy) && |
| (!lock.registrar() |
| .GetAppById(app_id.value()) |
| ->IsPolicyInstalledApp())) { |
| debug_value.Set("reinstalling_policy_app", true); |
| StartInstallationTask(std::move(front), |
| /*installed_placeholder_app_id=*/std::nullopt); |
| return; |
| } |
| |
| debug_value.Set("simple_source_addition", true); |
| // Add install source before returning the result. |
| ScopedRegistryUpdate update = lock.sync_bridge().BeginUpdate(); |
| WebApp* app_to_update = update->UpdateApp(app_id.value()); |
| app_to_update->AddSource( |
| ConvertExternalInstallSourceToSource(install_options.install_source)); |
| app_to_update->AddInstallURLToManagementExternalConfigMap( |
| ConvertExternalInstallSourceToSource(install_options.install_source), |
| install_options.install_url); |
| |
| std::move(front->callback) |
| .Run(install_options.install_url, |
| ExternallyManagedAppManagerInstallResult( |
| webapps::InstallResultCode::kSuccessAlreadyInstalled, |
| app_id)); |
| continue; |
| } |
| |
| // If neither of the above conditions applies, the app probably got |
| // uninstalled but it wasn't been removed from the map. We should install |
| // the app in this case. |
| StartInstallationTask(std::move(front), |
| /*installed_placeholder_app_id=*/std::nullopt); |
| return; |
| } |
| DCHECK(!current_install_metadata_); |
| |
| if (current_registration_ || RunNextRegistration()) { |
| return; |
| } |
| |
| ReleaseWebContents(); |
| } |
| |
| void ExternallyManagedAppManager::StartInstallationTask( |
| std::unique_ptr<ExternalInstallMetadata> external_install_metadata, |
| std::optional<webapps::AppId> installed_placeholder_app_id) { |
| if (IsShuttingDown()) { |
| return; |
| } |
| DCHECK(!current_install_metadata_); |
| DCHECK(!is_in_shutdown_); |
| if (current_registration_) { |
| // Preempt current registration. |
| pending_registrations_.push_front( |
| {current_registration_->install_url(), |
| current_registration_->registration_timeout()}); |
| current_registration_.reset(); |
| } |
| |
| current_install_metadata_ = std::move(external_install_metadata); |
| CreateWebContentsIfNecessary(); |
| provider_->scheduler().InstallExternallyManagedApp( |
| current_install_metadata_->options, |
| std::move(installed_placeholder_app_id), |
| base::BindOnce(&ExternallyManagedAppManager::OnInstalled, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| bool ExternallyManagedAppManager::RunNextRegistration() { |
| if (pending_registrations_.empty() || IsShuttingDown()) { |
| if (registrations_complete_callback_) { |
| std::move(registrations_complete_callback_).Run(); |
| } |
| return false; |
| } |
| |
| const auto [url_to_check, registration_timeout] = |
| std::move(pending_registrations_.front()); |
| pending_registrations_.pop_front(); |
| current_registration_ = |
| CreateRegistration(std::move(url_to_check), registration_timeout); |
| current_registration_->Start(); |
| return true; |
| } |
| |
| void ExternallyManagedAppManager::CreateWebContentsIfNecessary() { |
| DCHECK(!IsShuttingDown()); |
| if (web_contents_) { |
| return; |
| } |
| |
| web_contents_ = content::WebContents::Create( |
| content::WebContents::CreateParams(profile_)); |
| CreateWebAppInstallTabHelpers(web_contents_.get()); |
| } |
| |
| void ExternallyManagedAppManager::OnInstalled( |
| ExternallyManagedAppManagerInstallResult result) { |
| if (result.app_id && IsSuccess(result.code)) { |
| MaybeEnqueueServiceWorkerRegistration(current_install_metadata_->options); |
| } |
| |
| // Post a task to avoid webapps::InstallableManager crashing and do so before |
| // running the callback in case the callback tries to install another |
| // app. |
| PostMaybeStartNext(); |
| |
| std::unique_ptr<ExternalInstallMetadata> metadata_and_callback; |
| metadata_and_callback.swap(current_install_metadata_); |
| std::move(metadata_and_callback->callback) |
| .Run(metadata_and_callback->options.install_url, result); |
| } |
| |
| void ExternallyManagedAppManager::MaybeEnqueueServiceWorkerRegistration( |
| const ExternalInstallOptions& install_options) { |
| if (IsShuttingDown()) { |
| return; |
| } |
| |
| if (install_options.only_use_app_info_factory) { |
| return; |
| } |
| |
| if (!install_options.load_and_await_service_worker_registration) { |
| return; |
| } |
| |
| // TODO(crbug.com/40561535): Call CreateWebContentsIfNecessary() instead of |
| // checking web_contents_ once major migration of default hosted apps to web |
| // apps has completed. |
| // Temporarily using offline manifest migrations (in which |web_contents_| |
| // is nullptr) in order to avoid overwhelming migrated-to web apps with hits |
| // for service worker registrations. |
| if (!web_contents_) { |
| return; |
| } |
| |
| GURL url = install_options.service_worker_registration_url.value_or( |
| install_options.install_url); |
| if (url.is_empty()) { |
| return; |
| } |
| if (url.scheme() == content::kChromeUIScheme) { |
| return; |
| } |
| if (url.scheme() == content::kChromeUIUntrustedScheme) { |
| return; |
| } |
| |
| pending_registrations_.push_back( |
| {url, install_options.service_worker_registration_timeout}); |
| } |
| |
| bool ExternallyManagedAppManager::IsShuttingDown() { |
| return is_in_shutdown_ || profile()->ShutdownStarted(); |
| } |
| |
| void ExternallyManagedAppManager::InstallApps( |
| std::vector<ExternalInstallOptions> install_options_list, |
| const RepeatingInstallCallback& callback) { |
| if (DropRequestsForTesting()) { |
| CHECK_IS_TEST(); |
| return; |
| } |
| |
| for (auto& install_options : install_options_list) { |
| pending_installs_metadata_.push_back( |
| std::make_unique<ExternalInstallMetadata>(std::move(install_options), |
| callback)); |
| } |
| |
| PostMaybeStartNext(); |
| } |
| |
| void ExternallyManagedAppManager::UninstallApps( |
| std::vector<GURL> uninstall_urls, |
| ExternalInstallSource install_source, |
| const UninstallCallback& callback) { |
| for (auto& url : uninstall_urls) { |
| provider_->scheduler().RemoveInstallUrlMaybeUninstall( |
| /*app_id=*/std::nullopt, |
| ConvertExternalInstallSourceToSource(install_source), url, |
| ConvertExternalInstallSourceToUninstallSource(install_source), |
| base::BindOnce( |
| [](const UninstallCallback& callback, const GURL& app_url, |
| webapps::UninstallResultCode code) { |
| callback.Run(app_url, code); |
| }, |
| callback, url)); |
| } |
| } |
| |
| void ExternallyManagedAppManager::SynchronizeInstalledAppsOnLockAcquired( |
| std::vector<ExternalInstallOptions> desired_apps_install_options, |
| ExternalInstallSource install_source, |
| SynchronizeCallback callback, |
| AllAppsLock& lock, |
| base::Value::Dict& debug_info) { |
| CHECK(callback); |
| debug_info.Set("install_source", base::ToString(install_source)); |
| base::Value::List* desired_installs = |
| debug_info.EnsureList("desired_apps_install_options"); |
| for (const ExternalInstallOptions& option : desired_apps_install_options) { |
| desired_installs->Append(option.install_url.spec()); |
| } |
| std::vector<GURL> installed_urls; |
| for (const auto& [app_id, install_urls] : |
| lock.registrar().GetExternallyInstalledApps(install_source)) { |
| for (const GURL& url : install_urls) { |
| installed_urls.push_back(url); |
| } |
| } |
| |
| std::sort(installed_urls.begin(), installed_urls.end()); |
| |
| base::Value::List* desired_urls_debug = debug_info.EnsureList("desired_urls"); |
| std::vector<GURL> desired_urls; |
| desired_urls.reserve(desired_apps_install_options.size()); |
| for (const auto& info : desired_apps_install_options) { |
| desired_urls.push_back(info.install_url); |
| desired_urls_debug->Append(info.install_url.spec()); |
| } |
| |
| std::sort(desired_urls.begin(), desired_urls.end()); |
| |
| std::vector<GURL> urls_to_remove = |
| base::STLSetDifference<std::vector<GURL>>(installed_urls, desired_urls); |
| base::Value::List* urls_to_remove_debug = |
| debug_info.EnsureList("urls_to_remove"); |
| for (const GURL& url_to_remove : urls_to_remove) { |
| urls_to_remove_debug->Append(url_to_remove.spec()); |
| } |
| |
| // Run callback immediately if there's no work to be done. |
| if (urls_to_remove.empty() && desired_apps_install_options.empty()) { |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce(std::move(callback), std::map<GURL, InstallResult>(), |
| std::map<GURL, webapps::UninstallResultCode>())); |
| return; |
| } |
| |
| // Add the callback to a map and call once all installs/uninstalls finish. |
| synchronize_requests_.insert_or_assign( |
| install_source, |
| SynchronizeRequest(std::move(callback), |
| std::move(desired_apps_install_options), |
| urls_to_remove.size())); |
| |
| if (urls_to_remove.empty()) { |
| // If there are no uninstalls, this will trigger the installs. |
| ContinueSynchronization(install_source); |
| } else { |
| UninstallApps( |
| urls_to_remove, install_source, |
| base::BindRepeating( |
| &ExternallyManagedAppManager::UninstallForSynchronizeCallback, |
| weak_ptr_factory_.GetWeakPtr(), install_source)); |
| } |
| } |
| |
| void ExternallyManagedAppManager::SetRegistrationCallbackForTesting( |
| RegistrationCallback callback) { |
| registration_callback_ = std::move(callback); |
| } |
| |
| void ExternallyManagedAppManager::ClearRegistrationCallbackForTesting() { |
| registration_callback_ = RegistrationCallback(); |
| } |
| |
| void ExternallyManagedAppManager::SetRegistrationsCompleteCallbackForTesting( |
| base::OnceClosure callback) { |
| registrations_complete_callback_ = std::move(callback); |
| } |
| |
| void ExternallyManagedAppManager::InstallForSynchronizeCallback( |
| ExternalInstallSource source, |
| const GURL& install_url, |
| ExternallyManagedAppManagerInstallResult result) { |
| if (!IsSuccess(result.code)) { |
| LOG(ERROR) << install_url << " from install source " |
| << static_cast<int>(source) << " failed to install with reason " |
| << static_cast<int>(result.code); |
| } |
| |
| auto source_and_request = synchronize_requests_.find(source); |
| CHECK(source_and_request != synchronize_requests_.end(), |
| base::NotFatalUntil::M130); |
| SynchronizeRequest& request = source_and_request->second; |
| request.install_results[install_url] = std::move(result); |
| --request.remaining_install_requests; |
| DCHECK_GE(request.remaining_install_requests, 0); |
| |
| ContinueSynchronization(source); |
| } |
| |
| void ExternallyManagedAppManager::UninstallForSynchronizeCallback( |
| ExternalInstallSource source, |
| const GURL& install_url, |
| webapps::UninstallResultCode code) { |
| auto source_and_request = synchronize_requests_.find(source); |
| CHECK(source_and_request != synchronize_requests_.end(), |
| base::NotFatalUntil::M130); |
| SynchronizeRequest& request = source_and_request->second; |
| request.uninstall_results[install_url] = code; |
| --request.remaining_uninstall_requests; |
| DCHECK_GE(request.remaining_uninstall_requests, 0); |
| |
| ContinueSynchronization(source); |
| } |
| |
| void ExternallyManagedAppManager::ContinueSynchronization( |
| ExternalInstallSource source) { |
| auto source_and_request = synchronize_requests_.find(source); |
| CHECK(source_and_request != synchronize_requests_.end(), |
| base::NotFatalUntil::M130); |
| |
| SynchronizeRequest& request = source_and_request->second; |
| |
| if (request.remaining_uninstall_requests > 0) { |
| return; |
| } |
| |
| // Installs only take place after all uninstalls. |
| if (!request.pending_installs.empty()) { |
| DCHECK_GT(request.remaining_install_requests, 0); |
| // Note: It is intentional that std::move(request.pending_installs) clears |
| // the vector in `request`, preventing this branch from triggering again. |
| InstallApps(std::move(request.pending_installs), |
| base::BindRepeating( |
| &ExternallyManagedAppManager::InstallForSynchronizeCallback, |
| weak_ptr_factory_.GetWeakPtr(), source)); |
| return; |
| } |
| |
| if (request.remaining_install_requests > 0) { |
| return; |
| } |
| |
| provider_->scheduler().ScheduleDedupeInstallUrls( |
| base::BindOnce(&ExternallyManagedAppManager::CompleteSynchronization, |
| weak_ptr_factory_.GetWeakPtr(), source)); |
| } |
| |
| void ExternallyManagedAppManager::CompleteSynchronization( |
| ExternalInstallSource source) { |
| auto source_and_request = synchronize_requests_.find(source); |
| CHECK(source_and_request != synchronize_requests_.end(), |
| base::NotFatalUntil::M130); |
| |
| SynchronizeRequest& request = source_and_request->second; |
| CHECK(request.callback); |
| |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(std::move(request.callback), |
| std::move(request.install_results), |
| std::move(request.uninstall_results))); |
| synchronize_requests_.erase(source); |
| } |
| |
| void ExternallyManagedAppManager::ClearSynchronizeRequestsForTesting() { |
| synchronize_requests_.erase(synchronize_requests_.begin(), |
| synchronize_requests_.end()); |
| } |
| |
| std::ostream& operator<<( |
| std::ostream& out, |
| const ExternallyManagedAppManagerInstallResult& install_result) { |
| base::Value::Dict output; |
| output.Set("code", base::ToString(install_result.code)); |
| output.Set("app_id", base::ToString(install_result.app_id)); |
| output.Set("did_uninstall_and_replace", |
| install_result.did_uninstall_and_replace); |
| out << output.DebugString(); |
| return out; |
| } |
| |
| } // namespace web_app |