| // 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/commands/external_app_resolution_command.h" |
| |
| #include <memory> |
| #include <optional> |
| #include <utility> |
| |
| #include "base/barrier_closure.h" |
| #include "base/containers/extend.h" |
| #include "base/containers/flat_set.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_forward.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/notreached.h" |
| #include "base/strings/to_string.h" |
| #include "base/values.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/web_applications/commands/web_app_command.h" |
| #include "chrome/browser/web_applications/commands/web_app_uninstall_command.h" |
| #include "chrome/browser/web_applications/external_install_options.h" |
| #include "chrome/browser/web_applications/externally_managed_app_manager.h" |
| #include "chrome/browser/web_applications/jobs/install_from_info_job.h" |
| #include "chrome/browser/web_applications/jobs/install_placeholder_job.h" |
| #include "chrome/browser/web_applications/jobs/uninstall/remove_install_url_job.h" |
| #include "chrome/browser/web_applications/jobs/uninstall/web_app_uninstall_and_replace_job.h" |
| #include "chrome/browser/web_applications/locks/all_apps_lock.h" |
| #include "chrome/browser/web_applications/locks/shared_web_contents_with_app_lock.h" |
| #include "chrome/browser/web_applications/locks/web_app_lock_manager.h" |
| #include "chrome/browser/web_applications/proto/web_app.pb.h" |
| #include "chrome/browser/web_applications/web_app_command_manager.h" |
| #include "chrome/browser/web_applications/web_app_command_scheduler.h" |
| #include "chrome/browser/web_applications/web_app_constants.h" |
| #include "chrome/browser/web_applications/web_app_helpers.h" |
| #include "chrome/browser/web_applications/web_app_icon_operations.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_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_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/services/app_service/public/cpp/app_launch_util.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 "components/webapps/common/web_app_id.h" |
| #include "content/public/browser/web_contents.h" |
| |
| namespace web_app { |
| |
| namespace { |
| // Returns a copy of `other` retaining only the fields that are needed for |
| // a shortcut (e.g icons), and using the document title and URL instead of |
| // manifest properties. This will strip out app-like fields (e.g. file |
| // handlers). |
| static WebAppInstallInfo CreateInstallInfoForCreateDiy( |
| const GURL& document_url, |
| const std::u16string& document_title, |
| const WebAppInstallInfo& other) { |
| WebAppInstallInfo create_diy_app_info( |
| GenerateManifestIdFromStartUrlOnly(document_url), document_url); |
| create_diy_app_info.title = document_title; |
| create_diy_app_info.description = other.description; |
| create_diy_app_info.manifest_url = other.manifest_url; |
| create_diy_app_info.manifest_icons = other.manifest_icons; |
| create_diy_app_info.icon_bitmaps = other.icon_bitmaps; |
| create_diy_app_info.other_icon_bitmaps = other.other_icon_bitmaps; |
| create_diy_app_info.is_generated_icon = other.is_generated_icon; |
| create_diy_app_info.theme_color = other.theme_color; |
| create_diy_app_info.dark_mode_theme_color = other.dark_mode_theme_color; |
| create_diy_app_info.background_color = other.background_color; |
| create_diy_app_info.dark_mode_background_color = |
| other.dark_mode_background_color; |
| create_diy_app_info.display_mode = other.display_mode; |
| create_diy_app_info.display_override = other.display_override; |
| create_diy_app_info.additional_search_terms = other.additional_search_terms; |
| create_diy_app_info.install_url = other.install_url; |
| create_diy_app_info.is_diy_app = true; |
| |
| return create_diy_app_info; |
| } |
| } // namespace |
| |
| ExternalAppResolutionCommand::ExternalAppResolutionCommand( |
| Profile& profile, |
| const ExternalInstallOptions& install_options, |
| std::optional<webapps::AppId> installed_placeholder_app_id, |
| InstalledCallback installed_callback) |
| : WebAppCommand<SharedWebContentsLock, |
| ExternallyManagedAppManager::InstallResult>( |
| "ExternalAppResolutionCommand", |
| SharedWebContentsLockDescription(), |
| std::move(installed_callback), |
| /*args_for_shutdown=*/ |
| ExternallyManagedAppManager::InstallResult( |
| webapps::InstallResultCode:: |
| kCancelledOnWebAppProviderShuttingDown, |
| std::nullopt, |
| /*did_uninstall_and_replace=*/false)), |
| profile_(profile), |
| install_options_(install_options), |
| installed_placeholder_app_id_(std::move(installed_placeholder_app_id)), |
| install_surface_(ConvertExternalInstallSourceToInstallSource( |
| install_options_.install_source)), |
| install_error_log_entry_(/*background_installation=*/true, |
| install_surface_) { |
| GetMutableDebugValue().Set("external_install_options", |
| install_options_.AsDebugValue()); |
| GetMutableDebugValue().Set( |
| "installed_placeholder_app_id", |
| installed_placeholder_app_id_.has_value() |
| ? base::ToString(installed_placeholder_app_id_.value()) |
| : "nullopt"); |
| } |
| |
| ExternalAppResolutionCommand::~ExternalAppResolutionCommand() = default; |
| |
| void ExternalAppResolutionCommand::SetDataRetrieverForTesting( |
| std::unique_ptr<WebAppDataRetriever> data_retriever) { |
| data_retriever_ = std::move(data_retriever); |
| } |
| |
| void ExternalAppResolutionCommand::SetOnLockUpgradedCallbackForTesting( |
| base::OnceClosure callback) { |
| on_lock_upgraded_callback_for_testing_ = std::move(callback); |
| } |
| |
| void ExternalAppResolutionCommand::OnShutdown( |
| base::PassKey<WebAppCommandManager>) const { |
| webapps::InstallableMetrics::TrackInstallResult(false, install_surface_); |
| } |
| |
| void ExternalAppResolutionCommand::StartWithLock( |
| std::unique_ptr<SharedWebContentsLock> lock) { |
| web_contents_lock_ = std::move(lock); |
| web_contents_ = &web_contents_lock_->shared_web_contents(); |
| url_loader_ = web_contents_lock_->web_contents_manager().CreateUrlLoader(); |
| if (!data_retriever_) { |
| data_retriever_ = |
| web_contents_lock_->web_contents_manager().CreateDataRetriever(); |
| } |
| CHECK(web_contents_ && !web_contents_->IsBeingDestroyed()); |
| |
| if (install_options_.only_use_app_info_factory) { |
| InstallFromInfo(); |
| return; |
| } |
| |
| url_loader_->LoadUrl( |
| install_options_.install_url, web_contents_.get(), |
| webapps::WebAppUrlLoader::UrlComparison::kSameOrigin, |
| base::BindOnce( |
| &ExternalAppResolutionCommand::OnUrlLoadedAndBranchInstallation, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| WebAppProvider& ExternalAppResolutionCommand::provider() const { |
| WebAppProvider* provider = WebAppProvider::GetForWebApps(&profile_.get()); |
| CHECK(provider); |
| return *provider; |
| } |
| |
| void ExternalAppResolutionCommand::Abort(webapps::InstallResultCode code) { |
| GetMutableDebugValue().Set("abort_result_code", base::ToString(code)); |
| webapps::InstallableMetrics::TrackInstallResult(false, install_surface_); |
| CompleteAndSelfDestruct(CommandResult::kFailure, |
| ExternallyManagedAppManager::InstallResult( |
| code, std::nullopt, |
| /*did_uninstall_and_replace=*/false)); |
| } |
| |
| void ExternalAppResolutionCommand::OnUrlLoadedAndBranchInstallation( |
| webapps::WebAppUrlLoaderResult result) { |
| // This is the main entry point for the command after the initial URL load. |
| // From here, we branch into one of three paths: |
| // 1. The URL loaded successfully, so we proceed with a regular web app |
| // installation. |
| // 2. The URL failed to load, but we are configured to install a placeholder |
| // app as a fallback. |
| // 3. The URL failed to load and we are not installing a placeholder, so we |
| // try to install from the app info factory as a last resort. |
| GetMutableDebugValue().Set("load_url_result", base::ToString(result)); |
| if (result == webapps::WebAppUrlLoaderResult::kUrlLoaded) { |
| data_retriever_->GetWebAppInstallInfo( |
| web_contents_.get(), |
| base::BindOnce( |
| &ExternalAppResolutionCommand::OnGetWebAppInstallInfoInCommand, |
| weak_ptr_factory_.GetWeakPtr())); |
| return; |
| } |
| |
| // If we could not load the URL and we are supposed to install a placeholder |
| // as fallback, acquire with the app id derived from the install url and lock |
| // this app id to see if this placeholder is already installed. |
| if (install_options_.install_placeholder) { |
| if (installed_placeholder_app_id_.has_value() && |
| !install_options_.force_reinstall) { |
| // No need to install a placeholder app again. |
| GetMutableDebugValue().Set("success", true); |
| CompleteAndSelfDestruct( |
| CommandResult::kSuccess, |
| PrepareResult( |
| /*is_offline_install=*/false, |
| ExternallyManagedAppManager::InstallResult( |
| webapps::InstallResultCode::kSuccessAlreadyInstalled, |
| *installed_placeholder_app_id_))); |
| return; |
| } |
| |
| CHECK(!apps_lock_); |
| apps_lock_ = std::make_unique<SharedWebContentsWithAppLock>(); |
| command_manager()->lock_manager().UpgradeAndAcquireLock( |
| std::move(web_contents_lock_), *apps_lock_, |
| {GenerateAppId(/*manifest_id_path=*/std::nullopt, |
| install_options_.install_url)}, |
| base::BindOnce( |
| &ExternalAppResolutionCommand::OnPlaceHolderAppLockAcquired, |
| weak_ptr_factory_.GetWeakPtr())); |
| return; |
| } |
| |
| // Avoid counting an error if we are shutting down. This matches later |
| // stages of install where if the WebContents is destroyed we return early. |
| if (result == webapps::WebAppUrlLoaderResult::kFailedWebContentsDestroyed) { |
| Abort(webapps::InstallResultCode::kWebContentsDestroyed); |
| return; |
| } |
| |
| webapps::InstallResultCode code = |
| webapps::InstallResultCode::kInstallURLLoadFailed; |
| |
| switch (result) { |
| case webapps::WebAppUrlLoaderResult::kUrlLoaded: |
| case webapps::WebAppUrlLoaderResult::kFailedWebContentsDestroyed: |
| // Handled above. |
| NOTREACHED(); |
| case webapps::WebAppUrlLoaderResult::kRedirectedUrlLoaded: |
| code = webapps::InstallResultCode::kInstallURLRedirected; |
| break; |
| case webapps::WebAppUrlLoaderResult::kFailedUnknownReason: |
| code = webapps::InstallResultCode::kInstallURLLoadFailed; |
| break; |
| case webapps::WebAppUrlLoaderResult::kFailedPageTookTooLong: |
| code = webapps::InstallResultCode::kInstallURLLoadTimeOut; |
| break; |
| case webapps::WebAppUrlLoaderResult::kFailedErrorPageLoaded: |
| code = webapps::InstallResultCode::kInstallURLLoadFailed; |
| break; |
| } |
| |
| TryAppInfoFactoryOnFailure(ExternallyManagedAppManager::InstallResult(code)); |
| } |
| |
| void ExternalAppResolutionCommand::OnGetWebAppInstallInfoInCommand( |
| std::unique_ptr<WebAppInstallInfo> web_app_info) { |
| // This is the first step of the main installation path. It is called after |
| // the initial URL has loaded and basic information about the page has been |
| // retrieved. |
| install_params_ = ConvertExternalInstallOptionsToParams(install_options_); |
| CHECK(install_params_.has_value()); |
| CHECK(web_contents_ && !web_contents_->IsBeingDestroyed()); |
| |
| web_app_info_ = std::move(web_app_info); |
| if (!web_app_info_) { |
| // TODO(b/297340562): Fallback to install from info. |
| Abort(webapps::InstallResultCode::kGetWebAppInstallInfoFailed); |
| return; |
| } |
| |
| // Write values from install_params_ to web_app_info. |
| // Set start_url to fallback_start_url as web_contents may have been |
| // redirected. Will be overridden by manifest values if present. |
| const GURL& start_url = install_params_->fallback_start_url; |
| CHECK(start_url.is_valid()); |
| web_app_info_->SetManifestIdAndStartUrl( |
| GenerateManifestIdFromStartUrlOnly(start_url), start_url); |
| |
| if (install_params_->fallback_app_name.has_value()) { |
| web_app_info_->title = install_params_->fallback_app_name.value(); |
| } |
| |
| data_retriever_->CheckInstallabilityAndRetrieveManifest( |
| web_contents_.get(), |
| base::BindOnce( |
| &ExternalAppResolutionCommand::OnDidPerformInstallableCheck, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void ExternalAppResolutionCommand::OnDidPerformInstallableCheck( |
| blink::mojom::ManifestPtr opt_manifest, |
| bool valid_manifest_for_web_app, |
| webapps::InstallableStatusCode error_code) { |
| CHECK(install_params_.has_value()); |
| CHECK(web_contents_ && !web_contents_->IsBeingDestroyed()); |
| CHECK(web_app_info_); |
| |
| if (install_params_->require_manifest && !valid_manifest_for_web_app) { |
| LOG(WARNING) << "Did not install " << web_app_info_->manifest_id().spec() |
| << " because it didn't have a manifest for web app"; |
| Abort(webapps::InstallResultCode::kNotValidManifestForWebApp); |
| return; |
| } |
| |
| // A system app should always have a manifest icon. |
| if (install_surface_ == webapps::WebappInstallSource::SYSTEM_DEFAULT) { |
| CHECK(opt_manifest); |
| CHECK(!opt_manifest->icons.empty()); |
| } |
| |
| GetMutableDebugValue().Set("had_manifest", opt_manifest ? true : false); |
| if (install_params_->install_as_diy) { |
| *web_app_info_ = CreateInstallInfoForCreateDiy( |
| web_contents_->GetLastCommittedURL(), web_contents_->GetTitle(), |
| *web_app_info_); |
| } |
| |
| // Follow the path with a valid `opt_manifest`. |
| if (opt_manifest) { |
| RetrieveWebAppInfoFromManifest(std::move(opt_manifest)); |
| return; |
| } |
| |
| // Follow the path to install using the existing `web_app_info_`, AKA, the |
| // fallback option. |
| IconUrlSizeSet icon_urls = GetValidIconUrlsToDownload(*web_app_info_); |
| if (!web_contents_->GetVisibleURL().EqualsIgnoringRef( |
| GURL(url::kAboutBlankURL))) { |
| // Navigate to about:blank. This ensure that no further |
| // navigation/redirection is still running that could interrupt icon |
| // fetching. |
| const GURL kAboutBlankURL = GURL(url::kAboutBlankURL); |
| url_loader_->LoadUrl( |
| kAboutBlankURL, web_contents_.get(), |
| webapps::WebAppUrlLoader::UrlComparison::kExact, |
| base::BindOnce(&ExternalAppResolutionCommand:: |
| OnPreparedForIconRetrievingForFallbackInfo, |
| weak_ptr_factory_.GetWeakPtr(), std::move(icon_urls))); |
| return; |
| } |
| OnPreparedForIconRetrievingForFallbackInfo( |
| std::move(icon_urls), webapps::WebAppUrlLoaderResult::kUrlLoaded); |
| } |
| |
| void ExternalAppResolutionCommand::RetrieveWebAppInfoFromManifest( |
| blink::mojom::ManifestPtr opt_manifest) { |
| CHECK(opt_manifest); |
| WebAppInstallInfoConstructOptions construct_options; |
| construct_options.download_page_favicons = opt_manifest->icons.empty(); |
| // For installations triggered via policy with `install_as_shortcut` set to |
| // true, the app needs resources fetched from the manifest and it should also |
| // behave like DIY apps. |
| construct_options.force_override_name = install_params_->install_as_diy; |
| |
| manifest_to_install_info_job_ = |
| ManifestToWebAppInstallInfoJob::CreateAndStart( |
| *opt_manifest, *data_retriever_.get(), |
| /*background_installation=*/true, install_surface_, |
| web_contents_->GetWeakPtr(), [](IconUrlSizeSet&) {}, |
| GetMutableDebugValue(), |
| base::BindOnce(&ExternalAppResolutionCommand:: |
| OnWebAppInstallInfoParsedFromManifest, |
| weak_ptr_factory_.GetWeakPtr()), |
| construct_options, web_app_info_->Clone()); |
| } |
| |
| void ExternalAppResolutionCommand::OnWebAppInstallInfoParsedFromManifest( |
| std::unique_ptr<WebAppInstallInfo> install_info) { |
| CHECK(install_params_.has_value()); |
| CHECK(web_contents_ && !web_contents_->IsBeingDestroyed()); |
| |
| web_app_info_ = std::move(install_info); |
| if (install_params_->install_as_diy) { |
| web_app_info_->is_diy_app = true; |
| const GURL document_url = web_contents_->GetLastCommittedURL(); |
| web_app_info_->SetManifestIdAndStartUrl( |
| GenerateManifestIdFromStartUrlOnly(document_url), document_url); |
| } |
| |
| UpdateInfoWithParamsAndUpgradeLock(web_app_info_->is_generated_icon); |
| } |
| |
| void ExternalAppResolutionCommand::OnPreparedForIconRetrievingForFallbackInfo( |
| IconUrlSizeSet icon_urls, |
| webapps::WebAppUrlLoaderResult result) { |
| // `skip_page_favicons` is false since the icons are no longer being read from |
| // the manifest, as a result of which favicons from the page will need to be |
| // used as the app icon. |
| data_retriever_->GetIcons( |
| web_contents_.get(), std::move(icon_urls), /*skip_page_favicons=*/false, |
| /*fail_all_if_any_fail=*/false, |
| base::BindOnce( |
| &ExternalAppResolutionCommand::OnIconsRetrievedUpgradeLockDescription, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void ExternalAppResolutionCommand::OnIconsRetrievedUpgradeLockDescription( |
| IconsDownloadedResult result, |
| IconsMap icons_map, |
| DownloadedIconsHttpResults icons_http_results) { |
| CHECK(install_params_.has_value()); |
| CHECK(web_contents_ && !web_contents_->IsBeingDestroyed()); |
| |
| PopulateProductIcons(web_app_info_.get(), &icons_map); |
| PopulateOtherIcons(web_app_info_.get(), icons_map); |
| |
| RecordDownloadedIconsResultAndHttpStatusCodes(result, icons_http_results); |
| |
| install_error_log_entry_.LogDownloadedIconsErrors( |
| *web_app_info_, result, icons_map, icons_http_results); |
| UpdateInfoWithParamsAndUpgradeLock(result != |
| IconsDownloadedResult::kCompleted); |
| } |
| |
| void ExternalAppResolutionCommand::UpdateInfoWithParamsAndUpgradeLock( |
| bool icon_download_failed) { |
| // TODO(b/300878868): Reject installation if the manifest id provided in the |
| // WebAppInstallForceList does not match the final manifest id. |
| app_id_ = GenerateAppIdFromManifestId(web_app_info_->manifest_id()); |
| CHECK(!app_id_.empty()); |
| GetMutableDebugValue().Set("app_id", app_id_); |
| |
| ApplyParamsToWebAppInstallInfo(*install_params_, *web_app_info_); |
| // Upgrade to app_lock_ |
| apps_lock_ = std::make_unique<SharedWebContentsWithAppLock>(); |
| command_manager()->lock_manager().UpgradeAndAcquireLock( |
| std::move(web_contents_lock_), *apps_lock_, {app_id_}, |
| base::BindOnce( |
| &ExternalAppResolutionCommand::OnLockUpgradedFinalizeInstall, |
| weak_ptr_factory_.GetWeakPtr(), icon_download_failed)); |
| } |
| |
| void ExternalAppResolutionCommand::OnLockUpgradedFinalizeInstall( |
| bool icon_download_failed) { |
| CHECK(apps_lock_->IsGranted()); |
| CHECK(install_params_.has_value() && !app_id_.empty()); |
| CHECK(web_contents_ && !web_contents_->IsBeingDestroyed()); |
| |
| if (on_lock_upgraded_callback_for_testing_) { |
| std::move(on_lock_upgraded_callback_for_testing_).Run(); |
| } |
| |
| WebAppInstallFinalizer::FinalizeOptions finalize_options(install_surface_); |
| finalize_options.overwrite_existing_manifest_fields = |
| install_params_->force_reinstall; |
| |
| ApplyParamsToFinalizeOptions(*install_params_, finalize_options); |
| |
| if (install_params_->user_display_mode.has_value()) { |
| web_app_info_->user_display_mode = install_params_->user_display_mode; |
| } |
| finalize_options.add_to_applications_menu = |
| install_params_->add_to_applications_menu; |
| finalize_options.add_to_desktop = install_params_->add_to_desktop; |
| finalize_options.add_to_quick_launch_bar = |
| install_params_->add_to_quick_launch_bar; |
| |
| if (apps_lock_->registrar().IsInstallState( |
| app_id_, {proto::InstallState::SUGGESTED_FROM_ANOTHER_DEVICE, |
| proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION, |
| proto::InstallState::INSTALLED_WITH_OS_INTEGRATION})) { |
| // If an installation is triggered for the same app but with a |
| // different install_url, then we overwrite the manifest fields. |
| // If icon downloads fail, then we would not overwrite the icon |
| // in the web_app DB. |
| finalize_options.overwrite_existing_manifest_fields = true; |
| finalize_options.skip_icon_writes_on_download_failure = |
| icon_download_failed; |
| } |
| apps_lock_->install_finalizer().FinalizeInstall( |
| *web_app_info_, finalize_options, |
| base::BindOnce(&ExternalAppResolutionCommand::OnInstallFinalized, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void ExternalAppResolutionCommand::OnInstallFinalized( |
| const webapps::AppId& app_id, |
| webapps::InstallResultCode code) { |
| CHECK(web_contents_ && !web_contents_->IsBeingDestroyed()); |
| install_code_ = code; |
| |
| GetMutableDebugValue().Set("install_code", base::ToString(code)); |
| if (!webapps::IsSuccess(code)) { |
| TryAppInfoFactoryOnFailure( |
| ExternallyManagedAppManager::InstallResult(code)); |
| return; |
| } |
| |
| CHECK_EQ(app_id, app_id_); |
| RecordWebAppInstallationTimestamp( |
| Profile::FromBrowserContext(web_contents_->GetBrowserContext()) |
| ->GetPrefs(), |
| app_id_, install_surface_); |
| |
| if (base::FeatureList::IsEnabled(features::kRecordWebAppDebugInfo)) { |
| if (install_error_log_entry_.HasErrorDict()) { |
| command_manager()->LogToInstallManager( |
| install_error_log_entry_.TakeErrorDict()); |
| } |
| } |
| |
| webapps::InstallableMetrics::TrackInstallResult(webapps::IsSuccess(code), |
| install_surface_); |
| |
| CHECK(apps_lock_); |
| |
| uninstall_and_replace_job_.emplace( |
| &profile_.get(), |
| *GetMutableDebugValue().EnsureDict("uninstall_and_replace_job"), |
| *apps_lock_, install_options_.uninstall_and_replace, app_id_, |
| base::BindOnce(&ExternalAppResolutionCommand:: |
| OnUninstallAndReplaceCompletedUninstallPlaceholder, |
| weak_ptr_factory_.GetWeakPtr())); |
| uninstall_and_replace_job_->Start(); |
| } |
| |
| void ExternalAppResolutionCommand:: |
| OnUninstallAndReplaceCompletedUninstallPlaceholder( |
| bool uninstall_triggered) { |
| CHECK(apps_lock_); |
| uninstalled_for_replace_ = uninstall_triggered; |
| GetMutableDebugValue().Set("uninstalled_for_replace", uninstall_triggered); |
| uninstall_and_replace_job_ = std::nullopt; |
| |
| const bool uninstall_placeholder = |
| installed_placeholder_app_id_.has_value() && |
| *installed_placeholder_app_id_ != app_id_; |
| GetMutableDebugValue().Set("uninstall_placeholder", uninstall_placeholder); |
| |
| if (!uninstall_placeholder) { |
| CompleteAndSelfDestruct( |
| CommandResult::kSuccess, |
| PrepareResult( |
| /*is_offline_install=*/false, |
| ExternallyManagedAppManager::InstallResult( |
| install_code_, app_id_, uninstalled_for_replace_))); |
| return; |
| } |
| |
| const bool is_placeholder_running = |
| installed_placeholder_app_id_.has_value() && |
| (provider().ui_manager().GetNumWindowsForApp( |
| *installed_placeholder_app_id_) > 0); |
| GetMutableDebugValue().Set("is_placeholder_running", is_placeholder_running); |
| |
| if (is_placeholder_running) { |
| provider().ui_manager().NotifyAppRelaunchState( |
| *installed_placeholder_app_id_, app_id_, web_app_info_->title, |
| profile_->GetWeakPtr(), AppRelaunchState::kAppClosingForRelaunch); |
| } |
| |
| relaunch_app_after_placeholder_uninstall_ = |
| install_options_.placeholder_resolution_behavior == |
| PlaceholderResolutionBehavior::kCloseAndRelaunch && |
| is_placeholder_running; |
| GetMutableDebugValue().Set("relaunch_app_after_placeholder_uninstall", |
| relaunch_app_after_placeholder_uninstall_); |
| |
| // Note: This practice of releasing the app lock and requesting a whole new |
| // lock is highly discouraged & very selectively OK for this one case. |
| all_apps_lock_description_ = std::make_unique<AllAppsLockDescription>(); |
| all_apps_lock_ = std::make_unique<AllAppsLock>(); |
| command_manager()->lock_manager().AcquireLock( |
| *all_apps_lock_description_, *all_apps_lock_, |
| base::BindOnce( |
| &ExternalAppResolutionCommand::OnAllAppsLockGrantedRemovePlaceholder, |
| weak_ptr_factory_.GetWeakPtr()), |
| FROM_HERE); |
| web_contents_ = nullptr; |
| apps_lock_.reset(); |
| } |
| |
| void ExternalAppResolutionCommand::OnAllAppsLockGrantedRemovePlaceholder() { |
| CHECK(all_apps_lock_->IsGranted()); |
| CHECK(installed_placeholder_app_id_); |
| |
| remove_placeholder_job_.emplace( |
| webapps::WebappUninstallSource::kPlaceholderReplacement, *profile_, |
| *GetMutableDebugValue().EnsureDict("remove_placeholder_job"), |
| *installed_placeholder_app_id_, |
| WebAppManagementTypes({ConvertExternalInstallSourceToSource( |
| install_options_.install_source)})); |
| |
| remove_placeholder_job_->Start( |
| *all_apps_lock_, |
| base::BindOnce( |
| &ExternalAppResolutionCommand::OnPlaceholderUninstalledMaybeRelaunch, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void ExternalAppResolutionCommand::OnPlaceholderUninstalledMaybeRelaunch( |
| webapps::UninstallResultCode result) { |
| remove_placeholder_job_ = std::nullopt; |
| GetMutableDebugValue().Set("placeholder_uninstall_result", |
| base::ToString(result)); |
| if (!relaunch_app_after_placeholder_uninstall_) { |
| CompleteAndSelfDestruct( |
| CommandResult::kSuccess, |
| PrepareResult(/*is_offline_install=*/false, |
| ExternallyManagedAppManager::InstallResult( |
| install_code_, app_id_, uninstalled_for_replace_))); |
| return; |
| } |
| |
| provider().ui_manager().NotifyAppRelaunchState( |
| *installed_placeholder_app_id_, app_id_, web_app_info_->title, |
| profile_->GetWeakPtr(), AppRelaunchState::kAppAboutToRelaunch); |
| provider().ui_manager().LaunchWebApp( |
| WebAppUiManager::CreateAppLaunchParamsWithoutWindowConfig( |
| app_id_, *base::CommandLine::ForCurrentProcess(), |
| /*current_directory=*/base::FilePath(), |
| /*url_handler_launch_url=*/std::nullopt, |
| /*protocol_handler_launch_url=*/std::nullopt, |
| /*file_launch_url=*/std::nullopt, /*launch_files=*/{}), |
| LaunchWebAppWindowSetting::kOverrideWithWebAppConfig, *profile_, |
| base::BindOnce(&ExternalAppResolutionCommand::OnLaunch, |
| weak_ptr_factory_.GetWeakPtr()), |
| *all_apps_lock_); |
| } |
| |
| void ExternalAppResolutionCommand::OnLaunch(base::WeakPtr<Browser>, |
| base::WeakPtr<content::WebContents>, |
| apps::LaunchContainer, |
| base::Value debug_value) { |
| GetMutableDebugValue().Set("launch", std::move(debug_value)); |
| provider().ui_manager().NotifyAppRelaunchState( |
| *installed_placeholder_app_id_, app_id_, web_app_info_->title, |
| profile_->GetWeakPtr(), AppRelaunchState::kAppRelaunched); |
| |
| CompleteAndSelfDestruct( |
| CommandResult::kSuccess, |
| PrepareResult(/*is_offline_install=*/false, |
| ExternallyManagedAppManager::InstallResult( |
| install_code_, app_id_, uninstalled_for_replace_))); |
| } |
| |
| void ExternalAppResolutionCommand::OnPlaceHolderAppLockAcquired() { |
| CHECK(apps_lock_); |
| // This is the entry point for the placeholder installation path. It is called |
| // after the initial URL load has failed and we have acquired a lock for the |
| // placeholder app ID. |
| CHECK(apps_lock_->IsGranted()); |
| if (on_lock_upgraded_callback_for_testing_) { |
| std::move(on_lock_upgraded_callback_for_testing_).Run(); |
| } |
| |
| // TODO(b/300878868): Use the manifest id specified in the |
| // `WebAppInstallForceList` to generate the placeholder app id. This is needed |
| // to make sure an in-place installation can be done. |
| install_placeholder_job_.emplace( |
| &profile_.get(), |
| *GetMutableDebugValue().EnsureDict("install_placeholder_job"), |
| install_options_, |
| base::BindOnce(&ExternalAppResolutionCommand::OnPlaceHolderInstalled, |
| weak_ptr_factory_.GetWeakPtr()), |
| *apps_lock_); |
| install_placeholder_job_->Start(); |
| } |
| |
| void ExternalAppResolutionCommand::OnPlaceHolderInstalled( |
| webapps::InstallResultCode code, |
| webapps::AppId app_id) { |
| install_placeholder_job_ = std::nullopt; |
| app_id_ = app_id; |
| install_code_ = code; |
| GetMutableDebugValue().Set("new_placeholder_app_id", app_id_); |
| GetMutableDebugValue().Set("placeholder_install_code", |
| base::ToString(install_code_)); |
| |
| uninstall_and_replace_job_.emplace( |
| &profile_.get(), |
| *GetMutableDebugValue().EnsureDict("uninstall_and_replace_job"), |
| *apps_lock_, install_options_.uninstall_and_replace, app_id, |
| base::BindOnce( |
| &ExternalAppResolutionCommand::OnUninstallAndReplaceCompleted, |
| weak_ptr_factory_.GetWeakPtr(), /*is_offline_install=*/false)); |
| uninstall_and_replace_job_->Start(); |
| } |
| |
| void ExternalAppResolutionCommand::InstallFromInfo() { |
| // This is the entry point for the offline installation path. It is called |
| // when the initial URL load fails and we are not installing a placeholder, or |
| // when `only_use_app_info_factory` is true. |
| install_params_ = ConvertExternalInstallOptionsToParams(install_options_); |
| CHECK(install_params_.has_value()); |
| |
| // Do not fetch web_app_origin_association data over network. |
| if (install_options_.only_use_app_info_factory) { |
| install_params_->skip_origin_association_validation = true; |
| } |
| |
| if (!install_options_.app_info_factory) { |
| Abort(webapps::InstallResultCode::kGetWebAppInstallInfoFailed); |
| return; |
| } |
| |
| web_app_info_ = install_options_.app_info_factory.Run(); |
| |
| if (!web_app_info_) { |
| Abort(webapps::InstallResultCode::kGetWebAppInstallInfoFailed); |
| return; |
| } |
| |
| base::Extend(web_app_info_->additional_search_terms, |
| std::move(install_params_->additional_search_terms)); |
| web_app_info_->install_url = install_params_->install_url; |
| |
| if (!apps_lock_) { |
| apps_lock_ = std::make_unique<SharedWebContentsWithAppLock>(); |
| command_manager()->lock_manager().UpgradeAndAcquireLock( |
| std::move(web_contents_lock_), *apps_lock_, |
| {GenerateAppIdFromManifestId(web_app_info_->manifest_id())}, |
| base::BindOnce( |
| &ExternalAppResolutionCommand::OnInstallFromInfoAppLockAcquired, |
| weak_ptr_factory_.GetWeakPtr())); |
| return; |
| } |
| CHECK(apps_lock_->IsGranted()); |
| OnInstallFromInfoAppLockAcquired(); |
| } |
| |
| void ExternalAppResolutionCommand::OnInstallFromInfoAppLockAcquired() { |
| CHECK(apps_lock_); |
| CHECK(apps_lock_->IsGranted()); |
| |
| install_from_info_job_.emplace( |
| &profile_.get(), |
| *GetMutableDebugValue().EnsureDict("install_from_info_job"), |
| std::move(web_app_info_), |
| /*overwrite_existing_manifest_fields=*/install_params_->force_reinstall, |
| install_surface_, *install_params_, |
| base::BindOnce(&ExternalAppResolutionCommand::OnInstallFromInfoCompleted, |
| weak_ptr_factory_.GetWeakPtr())); |
| install_from_info_job_->Start(apps_lock_.get()); |
| } |
| |
| void ExternalAppResolutionCommand::OnInstallFromInfoCompleted( |
| webapps::AppId app_id, |
| webapps::InstallResultCode code) { |
| install_from_info_job_ = std::nullopt; |
| app_id_ = app_id; |
| install_code_ = code; |
| GetMutableDebugValue().Set("install_from_info_app_id", app_id_); |
| GetMutableDebugValue().Set("install_from_info_install_code", |
| base::ToString(install_code_)); |
| |
| bool successful_install_from_info = webapps::IsSuccess(code); |
| GetMutableDebugValue().Set("successful_install_from_info", |
| successful_install_from_info); |
| if (!successful_install_from_info) { |
| Abort(code); |
| return; |
| } |
| |
| webapps::InstallableMetrics::TrackInstallResult(successful_install_from_info, |
| install_surface_); |
| |
| uninstall_and_replace_job_.emplace( |
| &profile_.get(), |
| *GetMutableDebugValue().EnsureDict("uninstall_and_replace_job"), |
| *apps_lock_, install_options_.uninstall_and_replace, app_id, |
| base::BindOnce( |
| &ExternalAppResolutionCommand::OnUninstallAndReplaceCompleted, |
| weak_ptr_factory_.GetWeakPtr(), /*is_offline_install=*/true)); |
| uninstall_and_replace_job_->Start(); |
| } |
| |
| void ExternalAppResolutionCommand::OnUninstallAndReplaceCompleted( |
| bool is_offline_install, |
| bool uninstall_triggered) { |
| uninstall_and_replace_job_ = std::nullopt; |
| GetMutableDebugValue().Set("uninstalled_for_replace", uninstall_triggered); |
| CompleteAndSelfDestruct( |
| webapps::IsSuccess(install_code_) ? CommandResult::kSuccess |
| : CommandResult::kFailure, |
| PrepareResult(is_offline_install, |
| ExternallyManagedAppManager::InstallResult( |
| install_code_, app_id_, uninstall_triggered))); |
| } |
| |
| void ExternalAppResolutionCommand::TryAppInfoFactoryOnFailure( |
| ExternallyManagedAppManager::InstallResult result) { |
| GetMutableDebugValue().Set("retry_app_info_factory_on_failure", |
| base::ToString(result.code)); |
| |
| if (!webapps::IsSuccess(result.code) && install_options_.app_info_factory) { |
| InstallFromInfo(); |
| return; |
| } |
| |
| Abort(result.code); |
| } |
| |
| ExternallyManagedAppManager::InstallResult |
| ExternalAppResolutionCommand::PrepareResult( |
| bool is_offline_install, |
| ExternallyManagedAppManager::InstallResult result) { |
| GetMutableDebugValue().Set("is_offline_install", is_offline_install); |
| GetMutableDebugValue().Set("result_code", base::ToString(result.code)); |
| if (!IsSuccess(result.code)) { |
| result.app_id = std::nullopt; |
| return result; |
| } |
| |
| if (is_offline_install) { |
| result.code = |
| install_options_.only_use_app_info_factory |
| ? webapps::InstallResultCode::kSuccessOfflineOnlyInstall |
| : webapps::InstallResultCode::kSuccessOfflineFallbackInstall; |
| } |
| return result; |
| } |
| |
| } // namespace web_app |