| // Copyright 2023 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/isolated_web_apps/isolated_web_app_apply_update_command.h" |
| |
| #include <memory> |
| #include <optional> |
| #include <ostream> |
| #include <sstream> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| |
| #include "base/check.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/functional/callback_forward.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/sequence_checker.h" |
| #include "base/strings/strcat.h" |
| #include "base/types/expected.h" |
| #include "base/values.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/web_applications/callback_utils.h" |
| #include "chrome/browser/web_applications/commands/web_app_command.h" |
| #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_install_command_helper.h" |
| #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_storage_location.h" |
| #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_version.h" |
| #include "chrome/browser/web_applications/isolated_web_apps/pending_install_info.h" |
| #include "chrome/browser/web_applications/locks/app_lock.h" |
| #include "chrome/browser/web_applications/web_app.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_utils.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_utils.h" |
| #include "chrome/browser/web_applications/web_contents/web_contents_manager.h" |
| #include "components/webapps/browser/install_result_code.h" |
| #include "components/webapps/browser/web_contents/web_app_url_loader.h" |
| #include "components/webapps/common/web_app_id.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/web_contents.h" |
| |
| namespace web_app { |
| |
| std::ostream& operator<<(std::ostream& os, |
| const IsolatedWebAppApplyUpdateCommandError& error) { |
| return os << "IsolatedWebAppApplyUpdateCommandError { " |
| "message = \"" |
| << error.message << "\" }."; |
| } |
| |
| IsolatedWebAppApplyUpdateCommand::IsolatedWebAppApplyUpdateCommand( |
| IsolatedWebAppUrlInfo url_info, |
| std::unique_ptr<content::WebContents> web_contents, |
| std::unique_ptr<ScopedKeepAlive> optional_keep_alive, |
| std::unique_ptr<ScopedProfileKeepAlive> optional_profile_keep_alive, |
| base::OnceCallback<void( |
| base::expected<void, IsolatedWebAppApplyUpdateCommandError>)> callback, |
| std::unique_ptr<IsolatedWebAppInstallCommandHelper> command_helper) |
| : WebAppCommand< |
| AppLock, |
| base::expected<void, IsolatedWebAppApplyUpdateCommandError>>( |
| "IsolatedWebAppApplyUpdateCommand", |
| AppLockDescription(url_info.app_id()), |
| std::move(callback), /*args_for_shutdown=*/ |
| base::unexpected(IsolatedWebAppApplyUpdateCommandError{ |
| .message = std::string("System is shutting down.")})), |
| url_info_(std::move(url_info)), |
| web_contents_(std::move(web_contents)), |
| optional_keep_alive_(std::move(optional_keep_alive)), |
| optional_profile_keep_alive_(std::move(optional_profile_keep_alive)), |
| command_helper_(std::move(command_helper)) { |
| DETACH_FROM_SEQUENCE(sequence_checker_); |
| |
| CHECK(web_contents_ != nullptr); |
| CHECK(optional_profile_keep_alive_ == nullptr || |
| &profile() == optional_profile_keep_alive_->profile()); |
| |
| GetMutableDebugValue().Set("app_id", url_info_.app_id()); |
| GetMutableDebugValue().Set("origin", url_info_.origin().Serialize()); |
| GetMutableDebugValue().Set("bundle_id", url_info_.web_bundle_id().id()); |
| GetMutableDebugValue().Set( |
| "bundle_type", static_cast<int>(url_info_.web_bundle_id().type())); |
| } |
| |
| IsolatedWebAppApplyUpdateCommand::~IsolatedWebAppApplyUpdateCommand() = default; |
| |
| void IsolatedWebAppApplyUpdateCommand::StartWithLock( |
| std::unique_ptr<AppLock> lock) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| lock_ = std::move(lock); |
| url_loader_ = lock_->web_contents_manager().CreateUrlLoader(); |
| |
| auto weak_ptr = weak_factory_.GetWeakPtr(); |
| RunChainedCallbacks( |
| base::BindOnce( |
| &IsolatedWebAppApplyUpdateCommand::CheckIfUpdateIsStillPending, |
| weak_ptr), |
| base::BindOnce(&IsolatedWebAppApplyUpdateCommand::CheckTrustAndSignatures, |
| weak_ptr), |
| base::BindOnce(&IsolatedWebAppApplyUpdateCommand::CreateStoragePartition, |
| weak_ptr), |
| base::BindOnce(&IsolatedWebAppApplyUpdateCommand::LoadInstallUrl, |
| weak_ptr), |
| base::BindOnce(&IsolatedWebAppApplyUpdateCommand:: |
| CheckInstallabilityAndRetrieveManifest, |
| weak_ptr), |
| base::BindOnce(&IsolatedWebAppApplyUpdateCommand:: |
| ValidateManifestAndCreateInstallInfo, |
| weak_ptr), |
| base::BindOnce(&IsolatedWebAppApplyUpdateCommand:: |
| RetrieveIconsAndPopulateInstallInfo, |
| weak_ptr), |
| base::BindOnce(&IsolatedWebAppApplyUpdateCommand::Finalize, weak_ptr)); |
| } |
| |
| void IsolatedWebAppApplyUpdateCommand::CheckIfUpdateIsStillPending( |
| base::OnceClosure next_step_callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| const WebApp* installed_app = |
| lock_->registrar().GetAppById(url_info_.app_id()); |
| if (installed_app == nullptr) { |
| ReportFailure("App is no longer installed."); |
| return; |
| } |
| if (!installed_app->isolation_data().has_value()) { |
| ReportFailure("Installed app is not an Isolated Web App."); |
| return; |
| } |
| const WebApp::IsolationData& isolation_data = |
| *installed_app->isolation_data(); |
| |
| if (!isolation_data.pending_update_info().has_value()) { |
| ReportFailure("Installed app does not have a pending update."); |
| return; |
| } |
| pending_update_info_ = *isolation_data.pending_update_info(); |
| |
| GetMutableDebugValue().Set("pending_update_info", |
| pending_update_info_->AsDebugValue()); |
| |
| if (isolation_data.version >= pending_update_info_->version) { |
| ReportFailure(base::StrCat({"Installed app is already on version ", |
| isolation_data.version.GetString(), |
| ". Cannot update to version ", |
| pending_update_info_->version.GetString()})); |
| return; |
| } |
| if (isolation_data.location.dev_mode() != |
| pending_update_info_->location.dev_mode()) { |
| std::stringstream s; |
| s << "Unable to update between dev-mode and non-dev-mode storage location " |
| "types (" |
| << isolation_data.location << " to " << pending_update_info_->location |
| << ")."; |
| ReportFailure(s.str()); |
| return; |
| } |
| |
| std::move(next_step_callback).Run(); |
| } |
| |
| void IsolatedWebAppApplyUpdateCommand::CheckTrustAndSignatures( |
| base::OnceClosure next_step_callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| command_helper_->CheckTrustAndSignatures( |
| IwaSourceWithMode::FromStorageLocation(profile().GetPath(), |
| pending_update_info_->location), |
| &profile(), |
| base::BindOnce( |
| &IsolatedWebAppApplyUpdateCommand::RunNextStepOnSuccess<void>, |
| weak_factory_.GetWeakPtr(), std::move(next_step_callback))); |
| } |
| |
| void IsolatedWebAppApplyUpdateCommand::CreateStoragePartition( |
| base::OnceClosure next_step_callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // TODO(cmfcmf): Maybe we should log somewhere when the storage partition is |
| // unexpectedly missing? |
| command_helper_->CreateStoragePartitionIfNotPresent(profile()); |
| std::move(next_step_callback).Run(); |
| } |
| |
| void IsolatedWebAppApplyUpdateCommand::LoadInstallUrl( |
| base::OnceClosure next_step_callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| command_helper_->LoadInstallUrl( |
| IwaSourceWithMode::FromStorageLocation(profile().GetPath(), |
| pending_update_info_->location), |
| *web_contents_.get(), *url_loader_.get(), |
| base::BindOnce( |
| &IsolatedWebAppApplyUpdateCommand::RunNextStepOnSuccess<void>, |
| weak_factory_.GetWeakPtr(), std::move(next_step_callback))); |
| } |
| |
| void IsolatedWebAppApplyUpdateCommand::CheckInstallabilityAndRetrieveManifest( |
| base::OnceCallback<void(IsolatedWebAppInstallCommandHelper::ManifestAndUrl)> |
| next_step_callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| command_helper_->CheckInstallabilityAndRetrieveManifest( |
| *web_contents_.get(), |
| base::BindOnce(&IsolatedWebAppApplyUpdateCommand::RunNextStepOnSuccess< |
| IsolatedWebAppInstallCommandHelper::ManifestAndUrl>, |
| weak_factory_.GetWeakPtr(), |
| std::move(next_step_callback))); |
| } |
| |
| void IsolatedWebAppApplyUpdateCommand::ValidateManifestAndCreateInstallInfo( |
| base::OnceCallback<void(WebAppInstallInfo)> next_step_callback, |
| IsolatedWebAppInstallCommandHelper::ManifestAndUrl manifest_and_url) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| base::expected<WebAppInstallInfo, std::string> install_info = |
| command_helper_->ValidateManifestAndCreateInstallInfo( |
| pending_update_info_->version, manifest_and_url); |
| RunNextStepOnSuccess(std::move(next_step_callback), std::move(install_info)); |
| } |
| |
| void IsolatedWebAppApplyUpdateCommand::RetrieveIconsAndPopulateInstallInfo( |
| base::OnceCallback<void(WebAppInstallInfo)> next_step_callback, |
| WebAppInstallInfo install_info) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| GetMutableDebugValue().Set("app_title", install_info.title); |
| |
| command_helper_->RetrieveIconsAndPopulateInstallInfo( |
| std::move(install_info), *web_contents_.get(), |
| base::BindOnce(&IsolatedWebAppApplyUpdateCommand::RunNextStepOnSuccess< |
| WebAppInstallInfo>, |
| weak_factory_.GetWeakPtr(), |
| std::move(next_step_callback))); |
| } |
| |
| void IsolatedWebAppApplyUpdateCommand::Finalize(WebAppInstallInfo info) { |
| lock_->install_finalizer().FinalizeUpdate( |
| info, base::BindOnce(&IsolatedWebAppApplyUpdateCommand::OnFinalized, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void IsolatedWebAppApplyUpdateCommand::OnFinalized( |
| const webapps::AppId& app_id, |
| webapps::InstallResultCode update_result_code) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| CHECK_EQ(app_id, url_info_.app_id()); |
| |
| if (update_result_code == |
| webapps::InstallResultCode::kSuccessAlreadyInstalled) { |
| ReportSuccess(); |
| } else { |
| std::stringstream ss; |
| ss << "Error during finalization: " << update_result_code; |
| ReportFailure(ss.str()); |
| } |
| } |
| |
| void IsolatedWebAppApplyUpdateCommand::ReportFailure(std::string_view message) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| IsolatedWebAppApplyUpdateCommandError error{.message = std::string(message)}; |
| GetMutableDebugValue().Set("result", "error: " + error.message); |
| |
| // If this command fails, then it is best to delete the pending update info |
| // from the database. A failed pending update is likely caused by a corrupted |
| // Web Bundle. Re-discovering the update and re-downloading the bundle may fix |
| // things. |
| auto weak_ptr = weak_factory_.GetWeakPtr(); |
| RunChainedCallbacks( |
| base::BindOnce(&IsolatedWebAppApplyUpdateCommand::CleanupOnFailure, |
| weak_ptr), |
| base::BindOnce(&IsolatedWebAppApplyUpdateCommand::CompleteAndSelfDestruct, |
| weak_ptr, CommandResult::kFailure, |
| base::unexpected(std::move(error)), FROM_HERE)); |
| } |
| |
| void IsolatedWebAppApplyUpdateCommand::CleanupOnFailure( |
| base::OnceClosure next_step_callback) { |
| base::OnceClosure update_callback = |
| pending_update_info_.has_value() |
| ? base::BindOnce(CleanupLocationIfOwned, profile().GetPath(), |
| pending_update_info_->location, |
| std::move(next_step_callback)) |
| : std::move(next_step_callback); |
| |
| ScopedRegistryUpdate update = lock_->sync_bridge().BeginUpdate( |
| // We don't really care whether committing the update succeeds or fails. |
| // However, we want to wait for the write of the database to disk, so that |
| // a potential crash during that write happens before the |
| // to-be-implemented cleanup system for no longer referenced Web Bundles |
| // kicks in. |
| base::IgnoreArgs<bool>(std::move(update_callback))); |
| |
| WebApp* web_app = update->UpdateApp(url_info_.app_id()); |
| |
| // This command might fail because the app is no longer installed, or because |
| // it does not have `WebApp::IsolationData` or |
| // `WebApp::IsolationData::PendingUpdateInfo`, in which case there is no |
| // pending update info for us to delete. |
| if (!web_app || !web_app->isolation_data().has_value() || |
| !web_app->isolation_data()->pending_update_info().has_value()) { |
| return; |
| } |
| |
| WebApp::IsolationData updated_isolation_data = *web_app->isolation_data(); |
| updated_isolation_data.SetPendingUpdateInfo(std::nullopt); |
| web_app->SetIsolationData(std::move(updated_isolation_data)); |
| } |
| |
| void IsolatedWebAppApplyUpdateCommand::ReportSuccess() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| GetMutableDebugValue().Set("result", "success"); |
| CompleteAndSelfDestruct(CommandResult::kSuccess, base::ok()); |
| } |
| |
| Profile& IsolatedWebAppApplyUpdateCommand::profile() { |
| CHECK(web_contents_); |
| CHECK(web_contents_->GetBrowserContext()); |
| return *Profile::FromBrowserContext(web_contents_->GetBrowserContext()); |
| } |
| |
| } // namespace web_app |