| // Copyright 2024 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/web_install_service_impl.h" |
| |
| #include <optional> |
| #include <vector> |
| |
| #include "base/functional/bind.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/web_applications/commands/web_install_from_url_command.h" |
| #include "chrome/browser/web_applications/locks/app_lock.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_helpers.h" |
| #include "chrome/browser/web_applications/web_app_install_info.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_tab_helper.h" |
| #include "chrome/browser/web_applications/web_app_ui_manager.h" |
| #include "chrome/browser/web_applications/web_app_utils.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 "components/permissions/permission_request.h" |
| #include "components/webapps/browser/banners/app_banner_manager.h" |
| #include "components/webapps/browser/banners/installable_web_app_check_result.h" |
| #include "components/webapps/browser/install_result_code.h" |
| #include "components/webapps/browser/installable/installable_logging.h" |
| #include "components/webapps/browser/installable/installable_metrics.h" |
| #include "components/webapps/browser/installable/installable_params.h" |
| #include "components/webapps/browser/installable/ml_install_operation_tracker.h" |
| #include "components/webapps/browser/installable/ml_installability_promoter.h" |
| #include "components/webapps/common/web_app_id.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/permission_controller.h" |
| #include "content/public/browser/permission_descriptor_util.h" |
| #include "content/public/browser/permission_request_description.h" |
| #include "content/public/browser/web_contents.h" |
| #include "third_party/blink/public/common/permissions/permission_utils.h" |
| #include "third_party/blink/public/mojom/permissions/permission_status.mojom.h" |
| #include "third_party/blink/public/mojom/web_install/web_install.mojom.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| |
| namespace web_app { |
| |
| namespace { |
| |
| using PermissionStatus = blink::mojom::PermissionStatus; |
| |
| constexpr SquareSizePx kIconSizeForLaunchDialog = 32; |
| constexpr char kInstallResultUma[] = "WebApp.WebInstallApi.Result"; |
| constexpr char kInstallTypeUma[] = "WebApp.WebInstallApi.InstallType"; |
| |
| // Checks if an app is installed based on `manifest_id`, if possible. Otherwise |
| // falls back to `install_target`. Used by the background doc install path. |
| // These are allowed to use unsafe registrar accesses, as this is the first step |
| // in a launch flow, and we later queue a command to launch, which will safely |
| // recheck the app's state in the registrar, and fail gracefully if it's no |
| // longer installed. |
| std::optional<webapps::AppId> IsAppInstalled( |
| Profile* profile, |
| const GURL& install_target, |
| const std::optional<GURL>& manifest_id) { |
| auto* provider = WebAppProvider::GetForWebApps(profile); |
| CHECK(provider); |
| |
| // Only consider apps that launch in a standalone window, or were installed |
| // by the user. |
| WebAppFilter filter = WebAppFilter::LaunchableFromInstallApi(); |
| |
| // If the developer provided a manifest ID, use it to look up the app. This |
| // avoids issues with nested app scopes and `install_target` potentially |
| // launching the wrong app. |
| if (manifest_id) { |
| webapps::AppId app_id_from_manifest_id = |
| GenerateAppIdFromManifestId(manifest_id.value()); |
| |
| bool found_app = provider->registrar_unsafe().AppMatches( |
| app_id_from_manifest_id, filter); |
| |
| return found_app ? std::optional<webapps::AppId>(app_id_from_manifest_id) |
| : std::nullopt; |
| } |
| |
| // No `manifest_id` was provided. Check for the app by `install_target`. This |
| // is less accurate and may result in another app being launched. |
| return provider->registrar_unsafe().FindBestAppWithUrlInScope(install_target, |
| filter); |
| } |
| |
| } // namespace |
| |
| WebInstallServiceImpl::WebInstallServiceImpl( |
| content::RenderFrameHost& render_frame_host, |
| mojo::PendingReceiver<blink::mojom::WebInstallService> receiver) |
| : content::DocumentService<blink::mojom::WebInstallService>( |
| render_frame_host, |
| std::move(receiver)), |
| frame_routing_id_(render_frame_host.GetGlobalId()) {} |
| |
| WebInstallServiceImpl::~WebInstallServiceImpl() = default; |
| |
| // static |
| void WebInstallServiceImpl::CreateIfAllowed( |
| content::RenderFrameHost* render_frame_host, |
| mojo::PendingReceiver<blink::mojom::WebInstallService> receiver) { |
| CHECK(render_frame_host); |
| |
| // This class is created only on the primary main frame. |
| if (!render_frame_host->IsInPrimaryMainFrame()) { |
| receiver.reset(); |
| return; |
| } |
| |
| if (!render_frame_host->GetLastCommittedURL().SchemeIsHTTPOrHTTPS()) { |
| receiver.reset(); |
| return; |
| } |
| |
| new WebInstallServiceImpl(*render_frame_host, std::move(receiver)); |
| } |
| |
| void WebInstallServiceImpl::Install(blink::mojom::InstallOptionsPtr options, |
| InstallCallback callback) { |
| // Wrap the blink callback in another that accepts all the information needed |
| // to log `kInstallResultUma`, then run run the blink callback. |
| auto callback_with_metrics = base::BindOnce( |
| [](InstallCallback callback, web_app::WebInstallApiResult metrics_result, |
| blink::mojom::WebInstallServiceResult install_result, |
| webapps::ManifestId manifest_id_result) { |
| base::UmaHistogramEnumeration(kInstallResultUma, metrics_result); |
| std::move(callback).Run(install_result, manifest_id_result); |
| }, |
| std::move(callback)); |
| |
| // Record the type of install being requested. Null `options` means no |
| // arguments were passed, which means a current document install. |
| base::UmaHistogramEnumeration( |
| kInstallTypeUma, options ? web_app::WebInstallApiType::kBackgroundDocument |
| : web_app::WebInstallApiType::kCurrentDocument); |
| |
| auto* rfh = content::RenderFrameHost::FromID(frame_routing_id_); |
| if (!rfh) { |
| std::move(callback_with_metrics) |
| .Run(web_app::WebInstallApiResult::kUnexpectedFailure, |
| blink::mojom::WebInstallServiceResult::kAbortError, |
| webapps::ManifestId()); |
| return; |
| } |
| |
| const GURL current_url = rfh->GetLastCommittedURL(); |
| GURL install_target = options ? GURL(options->install_url) : current_url; |
| |
| // Do not allow installation of file:// or chrome:// urls. |
| if (!install_target.SchemeIsHTTPOrHTTPS()) { |
| std::move(callback_with_metrics) |
| .Run(web_app::WebInstallApiResult::kUnexpectedFailure, |
| blink::mojom::WebInstallServiceResult::kAbortError, |
| webapps::ManifestId()); |
| return; |
| } |
| |
| // Installing web apps is not supported from off-the-record profiles. Show |
| // the install not supported dialog. |
| auto* profile = Profile::FromBrowserContext(rfh->GetBrowserContext()); |
| if (!AreWebAppsUserInstallable(profile)) { |
| WebAppUiManager::TriggerInstallNotSupportedDialog( |
| content::WebContents::FromRenderFrameHost(rfh), profile, |
| base::BindOnce( |
| &WebInstallServiceImpl::OnInstallNotSupportedDialogClosed, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback_with_metrics))); |
| return; |
| } |
| |
| // Initiate installation of the current document if no install options were |
| // given. |
| if (!options) { |
| TryInstallCurrentDocument(std::move(callback_with_metrics)); |
| |
| // Current document installs don't require the permissions checking code. |
| return; |
| } |
| |
| // Store the original install params for later. Current document doesn't need |
| // these, as only the 0 parameter signature can do current document installs. |
| install_options_ = std::move(options); |
| |
| // If the given url to install matches the current url, skip |
| // requesting permission since the user is still installing the current |
| // document, even though it's in the background. |
| if (install_target == current_url) { |
| OnPermissionDecided( |
| std::move(callback_with_metrics), |
| std::vector<PermissionStatus>({PermissionStatus::GRANTED})); |
| return; |
| } |
| |
| // Verify that the calling app has the Web Install permissions policy set. |
| if (!rfh->IsFeatureEnabled( |
| network::mojom::PermissionsPolicyFeature::kWebAppInstallation)) { |
| std::move(callback_with_metrics) |
| .Run(web_app::WebInstallApiResult::kPermissionDenied, |
| blink::mojom::WebInstallServiceResult::kAbortError, |
| webapps::ManifestId()); |
| return; |
| } |
| |
| RequestWebInstallPermission(base::BindOnce( |
| &WebInstallServiceImpl::OnPermissionDecided, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback_with_metrics))); |
| } |
| |
| void WebInstallServiceImpl::OnInstallNotSupportedDialogClosed( |
| InstallCallbackWithMetrics callback_with_metrics) { |
| std::move(callback_with_metrics) |
| .Run(web_app::WebInstallApiResult::kUnsupportedProfile, |
| blink::mojom::WebInstallServiceResult::kAbortError, |
| webapps::ManifestId()); |
| } |
| |
| void WebInstallServiceImpl::TryInstallCurrentDocument( |
| InstallCallbackWithMetrics callback_with_metrics) { |
| auto* web_contents = |
| content::WebContents::FromRenderFrameHost(&render_frame_host()); |
| auto* provider = WebAppProvider::GetForWebContents(web_contents); |
| CHECK(provider); |
| |
| // Check if the current document is already installed. |
| const webapps::AppId* app_id = |
| web_app::WebAppTabHelper::GetAppId(web_contents); |
| if (!app_id) { |
| // The current document is not installed yet. Retrieve the manifest to |
| // perform id validation checks. |
| std::unique_ptr<WebAppDataRetriever> data_retriever = |
| provider->web_contents_manager().CreateDataRetriever(); |
| webapps::InstallableParams params; |
| params.installable_criteria = |
| webapps::InstallableCriteria::kValidManifestWithIcons; |
| data_retriever->CheckInstallabilityAndRetrieveManifest( |
| web_contents, |
| base::BindOnce(&WebInstallServiceImpl:: |
| OnDidRetrieveManifestForCurrentDocumentInstall, |
| weak_ptr_factory_.GetWeakPtr(), |
| std::move(callback_with_metrics), provider), |
| params); |
| return; |
| } |
| // If the current document that is trying to install is currently in a PWA |
| // window, return kSuccessAlreadyInstalled. |
| web_app::WebAppTabHelper* tab_helper = |
| web_app::WebAppTabHelper::FromWebContents(web_contents); |
| if (tab_helper->is_in_app_window()) { |
| OnAppInstalled(std::move(callback_with_metrics), *app_id, |
| webapps::InstallResultCode::kSuccessAlreadyInstalled); |
| return; |
| } |
| |
| provider->scheduler().ScheduleCallback<AppLock>( |
| "WebInstallServiceImpl::TryInstallCurrentDocument", |
| AppLockDescription(*app_id), |
| base::BindOnce(&WebInstallServiceImpl::CheckForInstalledAppMaybeLaunch, |
| weak_ptr_factory_.GetWeakPtr(), web_contents, |
| std::move(callback_with_metrics)), |
| /*on_complete=*/base::DoNothing()); |
| } |
| |
| void WebInstallServiceImpl::CheckForInstalledAppMaybeLaunch( |
| content::WebContents* web_contents, |
| InstallCallbackWithMetrics callback_with_metrics, |
| AppLock& lock, |
| base::Value::Dict& debug_value) { |
| // Now that we've locked the app, re-confirm the current document is still |
| // already installed on the current device. |
| const webapps::AppId* app_id = |
| web_app::WebAppTabHelper::GetAppId(web_contents); |
| if (!app_id || !lock.registrar().AppMatches( |
| *app_id, web_app::WebAppFilter::InstalledInChrome())) { |
| std::move(callback_with_metrics) |
| .Run(web_app::WebInstallApiResult::kUnexpectedFailure, |
| blink::mojom::WebInstallServiceResult::kAbortError, |
| webapps::ManifestId()); |
| return; |
| } |
| |
| auto* provider = WebAppProvider::GetForWebContents(web_contents); |
| CHECK(provider); |
| |
| // Now that we know the app is already installed, show the intent picker. |
| provider->ui_manager().ShowIntentPicker( |
| web_contents->GetURL(), web_contents, |
| base::BindOnce(&WebInstallServiceImpl::OnIntentPickerMaybeLaunched, |
| weak_ptr_factory_.GetWeakPtr(), |
| std::move(callback_with_metrics), *app_id)); |
| } |
| |
| void WebInstallServiceImpl::OnIntentPickerMaybeLaunched( |
| InstallCallbackWithMetrics callback_with_metrics, |
| webapps::AppId app_id, |
| bool user_chose_to_open) { |
| // If the user chose to open the app in the intent picker, return success. |
| // Otherwise, return an abort error. |
| if (user_chose_to_open) { |
| OnAppInstalled(std::move(callback_with_metrics), app_id, |
| webapps::InstallResultCode::kSuccessAlreadyInstalled); |
| } else { |
| std::move(callback_with_metrics) |
| .Run(web_app::WebInstallApiResult::kSuccessAlreadyInstalled, |
| blink::mojom::WebInstallServiceResult::kAbortError, |
| webapps::ManifestId()); |
| } |
| } |
| |
| void WebInstallServiceImpl::OnDidRetrieveManifestForCurrentDocumentInstall( |
| InstallCallbackWithMetrics callback_with_metrics, |
| WebAppProvider* provider, |
| blink::mojom::ManifestPtr opt_manifest, |
| bool valid_manifest_for_web_app, |
| webapps::InstallableStatusCode error_code) { |
| // If for some reason a valid manifest was not found, cancel with the |
| // generic abort error. |
| if (!opt_manifest || !valid_manifest_for_web_app) { |
| std::move(callback_with_metrics) |
| .Run(web_app::WebInstallApiResult::kInstallCommandFailed, |
| blink::mojom::WebInstallServiceResult::kAbortError, |
| webapps::ManifestId()); |
| return; |
| } |
| // Ensure that the manifest is from the same trusted origin as the current |
| // document. |
| if (!origin().IsSameOriginWith(opt_manifest->id)) { |
| std::move(callback_with_metrics) |
| .Run(web_app::WebInstallApiResult::kInstallCommandFailed, |
| blink::mojom::WebInstallServiceResult::kAbortError, |
| webapps::ManifestId()); |
| return; |
| } |
| |
| // The manifest must have a developer-specified id since the current document |
| // version of navigator.install does not take a `manifest_id`. |
| if (!opt_manifest->has_custom_id) { |
| std::move(callback_with_metrics) |
| .Run(web_app::WebInstallApiResult::kNoCustomManifestId, |
| blink::mojom::WebInstallServiceResult::kDataError, |
| webapps::ManifestId()); |
| return; |
| } |
| |
| provider->ui_manager().TriggerInstallDialog( |
| content::WebContents::FromRenderFrameHost(&render_frame_host()), |
| webapps::WebappInstallSource::WEB_INSTALL, |
| base::BindOnce(&WebInstallServiceImpl::OnAppInstalled, |
| weak_ptr_factory_.GetWeakPtr(), |
| std::move(callback_with_metrics))); |
| } |
| |
| void WebInstallServiceImpl::RequestWebInstallPermission( |
| base::OnceCallback<void(const std::vector<PermissionStatus>&)> callback) { |
| content::BrowserContext* const browser_context = |
| render_frame_host().GetBrowserContext(); |
| if (!browser_context) { |
| std::move(callback).Run( |
| std::vector<PermissionStatus>({PermissionStatus::DENIED})); |
| return; |
| } |
| |
| content::PermissionController* const permission_controller = |
| browser_context->GetPermissionController(); |
| if (!permission_controller) { |
| std::move(callback).Run( |
| std::vector<PermissionStatus>({PermissionStatus::DENIED})); |
| return; |
| } |
| |
| // Check if the permission status is already set. |
| content::PermissionResult permission_status = |
| permission_controller->GetPermissionResultForCurrentDocument( |
| content::PermissionDescriptorUtil:: |
| CreatePermissionDescriptorForPermissionType( |
| blink::PermissionType::WEB_APP_INSTALLATION), |
| &render_frame_host()); |
| switch (permission_status.status) { |
| case PermissionStatus::GRANTED: |
| std::move(callback).Run( |
| std::vector<PermissionStatus>({PermissionStatus::GRANTED})); |
| return; |
| case PermissionStatus::DENIED: |
| case blink::mojom::PermissionStatus::UNSATISFIED_OPTIONS: |
| std::move(callback).Run( |
| std::vector<PermissionStatus>({PermissionStatus::DENIED})); |
| return; |
| case PermissionStatus::ASK: |
| break; |
| } |
| |
| GURL requesting_origin = origin().GetURL(); |
| // User gesture requirement is enforced in NavigatorWebInstall::InstallImpl. |
| permission_controller->RequestPermissionsFromCurrentDocument( |
| &render_frame_host(), |
| content::PermissionRequestDescription( |
| content::PermissionDescriptorUtil:: |
| CreatePermissionDescriptorForPermissionType( |
| blink::PermissionType::WEB_APP_INSTALLATION), |
| /*user_gesture=*/true, requesting_origin), |
| std::move(callback)); |
| } |
| |
| void WebInstallServiceImpl::OnPermissionDecided( |
| InstallCallbackWithMetrics callback_with_metrics, |
| const std::vector<PermissionStatus>& permission_status) { |
| CHECK_EQ(permission_status.size(), 1u); |
| if (permission_status[0] != PermissionStatus::GRANTED) { |
| std::move(callback_with_metrics) |
| .Run(web_app::WebInstallApiResult::kPermissionDenied, |
| blink::mojom::WebInstallServiceResult::kAbortError, |
| webapps::ManifestId()); |
| return; |
| } |
| |
| // Now that we have permission, verify that the current web contents is not |
| // already involved in an install operation. This protects against showing |
| // multiple dialogs, either install for the current or a background document, |
| // or a background document launch. |
| auto* web_contents = |
| content::WebContents::FromRenderFrameHost(&render_frame_host()); |
| webapps::MLInstallabilityPromoter* promoter = |
| webapps::MLInstallabilityPromoter::FromWebContents(web_contents); |
| CHECK(promoter); |
| if (promoter->HasCurrentInstall()) { |
| // The current web contents is being installed via another method. Cancel |
| // this background install/launch flow. |
| std::move(callback_with_metrics) |
| .Run(web_app::WebInstallApiResult::kUnexpectedFailure, |
| blink::mojom::WebInstallServiceResult::kAbortError, |
| webapps::ManifestId()); |
| return; |
| } |
| |
| auto* profile = |
| Profile::FromBrowserContext(render_frame_host().GetBrowserContext()); |
| auto* provider = WebAppProvider::GetForWebApps(profile); |
| CHECK(provider); |
| if (provider->command_manager().IsInstallingForWebContents(web_contents)) { |
| // Another install is already scheduled on the current web contents. |
| // Cancel this background install/launch flow. |
| std::move(callback_with_metrics) |
| .Run(web_app::WebInstallApiResult::kUnexpectedFailure, |
| blink::mojom::WebInstallServiceResult::kAbortError, |
| webapps::ManifestId()); |
| return; |
| } |
| |
| // Check if the background document is already installed so we can show the |
| // launch dialog instead of the install dialog. See definition for details |
| // on how we check if the app is installed. |
| std::optional<webapps::AppId> app_id = IsAppInstalled( |
| profile, install_options_->install_url, install_options_->manifest_id); |
| if (app_id) { |
| // See `IsAppInstalled` for why this can be unsafe. |
| const GURL& installed_manifest_id = |
| provider->registrar_unsafe().GetComputedManifestId(app_id.value()); |
| CHECK(!installed_manifest_id.is_empty()); |
| |
| // Get the information to display in the launch dialog. |
| provider->scheduler().FetchInstallInfoFromInstallUrl( |
| installed_manifest_id, install_options_->install_url, |
| base::BindOnce( |
| &WebInstallServiceImpl::OnInstallInfoFromInstallUrlFetched, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback_with_metrics), |
| app_id.value(), installed_manifest_id)); |
| return; |
| } |
| |
| // `install_url` was not installed. Proceed with the background install. |
| |
| // Register the background install on the current web contents. |
| std::unique_ptr<webapps::MlInstallOperationTracker> install_tracker = |
| promoter->RegisterCurrentInstallForWebContents( |
| webapps::WebappInstallSource::WEB_INSTALL); |
| |
| provider->ui_manager().TriggerInstallDialogForBackgroundInstall( |
| web_contents, std::move(install_tracker), install_options_->install_url, |
| install_options_->manifest_id, |
| base::BindOnce(&WebInstallServiceImpl::OnAppInstalled, |
| weak_ptr_factory_.GetWeakPtr(), |
| std::move(callback_with_metrics))); |
| } |
| |
| void WebInstallServiceImpl::OnInstallInfoFromInstallUrlFetched( |
| InstallCallbackWithMetrics callback_with_metrics, |
| webapps::AppId app_id, |
| const GURL& manifest_id, |
| std::unique_ptr<WebAppInstallInfo> install_info) { |
| // Choose the icon bitmap based on OS specific icon guidelines. See |
| // crbug.com/423906188 for more information. Regardless of OS, we expect an |
| // icon of size 32x32 to be available. |
| SkBitmap icon_bitmap; |
| IconBitmaps& icon_bitmaps = install_info->icon_bitmaps; |
| #if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS) |
| if (icon_bitmaps.maskable.empty()) { |
| CHECK(icon_bitmaps.any.contains(kIconSizeForLaunchDialog)); |
| icon_bitmap = icon_bitmaps.any[kIconSizeForLaunchDialog]; |
| } else { |
| CHECK(icon_bitmaps.maskable.contains(kIconSizeForLaunchDialog)); |
| icon_bitmap = icon_bitmaps.maskable[kIconSizeForLaunchDialog]; |
| } |
| #else |
| CHECK(icon_bitmaps.any.contains(kIconSizeForLaunchDialog)); |
| icon_bitmap = icon_bitmaps.any[kIconSizeForLaunchDialog]; |
| #endif // BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS) |
| |
| // Name to display in the dialog. |
| std::u16string app_title = install_info->title; |
| base::TrimWhitespace(app_title, base::TRIM_ALL, &app_title); |
| |
| auto* profile = |
| Profile::FromBrowserContext(render_frame_host().GetBrowserContext()); |
| auto* provider = WebAppProvider::GetForWebApps(profile); |
| CHECK(provider); |
| auto* web_contents = |
| content::WebContents::FromRenderFrameHost(&render_frame_host()); |
| |
| provider->ui_manager().TriggerLaunchDialogForBackgroundInstall( |
| web_contents, app_id, profile, base::UTF16ToUTF8(app_title), icon_bitmap, |
| base::BindOnce(&WebInstallServiceImpl::OnBackgroundAppLaunchDialogClosed, |
| weak_ptr_factory_.GetWeakPtr(), |
| std::move(callback_with_metrics), manifest_id)); |
| } |
| |
| void WebInstallServiceImpl::OnBackgroundAppLaunchDialogClosed( |
| InstallCallbackWithMetrics callback_with_metrics, |
| const GURL& manifest_id, |
| bool accepted) { |
| // For privacy reasons, only resolve with WebInstallServiceResult::kSuccess if |
| // the user accepted. |
| std::move(callback_with_metrics) |
| .Run(web_app::WebInstallApiResult::kSuccessAlreadyInstalled, |
| accepted ? blink::mojom::WebInstallServiceResult::kSuccess |
| : blink::mojom::WebInstallServiceResult::kAbortError, |
| accepted ? manifest_id : webapps::ManifestId()); |
| } |
| |
| void WebInstallServiceImpl::OnAppInstalled( |
| InstallCallbackWithMetrics callback_with_metrics, |
| const webapps::AppId& app_id, |
| webapps::InstallResultCode code) { |
| // Results to report for generic failures. |
| blink::mojom::WebInstallServiceResult install_result = |
| blink::mojom::WebInstallServiceResult::kAbortError; |
| webapps::ManifestId manifest_id_result; |
| // Catch all for any other failures during the install commands. For more |
| // fine grained error codes, see the histogram emitted by the commands - See |
| // "WebApp.InstallCommand{InstallCommand}{WebAppType}.ResultCode", and |
| // `RecordInstallMetrics` in `command_metrics.h`. |
| web_app::WebInstallApiResult uma_result = |
| web_app::WebInstallApiResult::kInstallCommandFailed; |
| |
| if (webapps::IsSuccess(code)) { |
| install_result = blink::mojom::WebInstallServiceResult::kSuccess; |
| |
| if (webapps::IsNewInstall(code)) { |
| uma_result = web_app::WebInstallApiResult::kSuccess; |
| } else if (code == webapps::InstallResultCode::kSuccessAlreadyInstalled) { |
| uma_result = web_app::WebInstallApiResult::kSuccessAlreadyInstalled; |
| } |
| |
| auto* profile = |
| Profile::FromBrowserContext(render_frame_host().GetBrowserContext()); |
| auto* provider = WebAppProvider::GetForWebApps(profile); |
| CHECK(provider); |
| |
| manifest_id_result = |
| provider->registrar_unsafe().GetComputedManifestId(app_id); |
| CHECK(!manifest_id_result.is_empty()); |
| } else if (code == webapps::InstallResultCode::kNoCustomManifestId) { |
| install_result = blink::mojom::WebInstallServiceResult::kDataError; |
| uma_result = web_app::WebInstallApiResult::kNoCustomManifestId; |
| } else if (code == webapps::InstallResultCode::kManifestIdMismatch) { |
| install_result = blink::mojom::WebInstallServiceResult::kDataError; |
| uma_result = web_app::WebInstallApiResult::kManifestIdMismatch; |
| } else if (code == webapps::InstallResultCode::kUserInstallDeclined) { |
| uma_result = web_app::WebInstallApiResult::kCanceledByUser; |
| } |
| |
| std::move(callback_with_metrics) |
| .Run(uma_result, install_result, manifest_id_result); |
| } |
| |
| } // namespace web_app |