| // Copyright 2021 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/ui/web_applications/sub_apps_service_impl.h" |
| |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/check.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/concurrent_callbacks.h" |
| #include "base/i18n/message_formatter.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/types/expected.h" |
| #include "base/types/expected_macros.h" |
| #include "chrome/browser/notifications/notification_display_service.h" |
| #include "chrome/browser/notifications/notification_display_service_factory.h" |
| #include "chrome/browser/notifications/notification_handler.h" |
| #include "chrome/browser/permissions/permission_decision_auto_blocker_factory.h" |
| #include "chrome/browser/policy/policy_util.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/web_applications/sub_apps_install_dialog_controller.h" |
| #include "chrome/browser/web_applications/web_app.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_finalizer.h" |
| #include "chrome/browser/web_applications/web_app_install_params.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_tab_helper.h" |
| #include "chrome/browser/web_applications/web_app_utils.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "components/content_settings/core/common/content_settings_types.h" |
| #include "components/permissions/permission_decision_auto_blocker.h" |
| #include "components/webapps/browser/installable/installable_metrics.h" |
| #include "components/webapps/browser/uninstall_result_code.h" |
| #include "components/webapps/common/web_app_id.h" |
| #include "content/public/browser/isolated_context_util.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/message_center/public/cpp/notification.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| #include "ash/constants/notifier_catalogs.h" |
| #endif |
| |
| using blink::mojom::SubAppsService; |
| using blink::mojom::SubAppsServiceAddParametersPtr; |
| using blink::mojom::SubAppsServiceAddResult; |
| using blink::mojom::SubAppsServiceAddResultPtr; |
| using blink::mojom::SubAppsServiceListResult; |
| using blink::mojom::SubAppsServiceListResultEntry; |
| using blink::mojom::SubAppsServiceListResultEntryPtr; |
| using blink::mojom::SubAppsServiceRemoveResult; |
| using blink::mojom::SubAppsServiceRemoveResultPtr; |
| using blink::mojom::SubAppsServiceResultCode; |
| |
| namespace web_app { |
| |
| // TODO(crbug.com/40924576): Replace registrar_unsafe with Locks |
| // TODO (crbug.com/1467862): Move from //c/b/ui/web_applications to |
| // //c/b/web_applications |
| |
| namespace { |
| |
| constexpr char kSubAppsUninstallNotifierId[] = "sub_apps_service"; |
| |
| // Resolve string `path` with `origin`, and if the resulting GURL isn't same |
| // origin with `origin` then return an error (for which the caller needs to |
| // raise a `ReportBadMessageAndDeleteThis`). |
| base::expected<GURL, std::string> ConvertPathToUrl(const std::string& path, |
| const url::Origin& origin) { |
| GURL resolved = origin.GetURL().Resolve(path); |
| |
| if (!origin.IsSameOriginWith(resolved)) { |
| return base::unexpected( |
| "SubAppsServiceImpl: Different origin arg to that of the calling app."); |
| } |
| |
| if (resolved.is_empty()) { |
| return base::unexpected("SubAppsServiceImpl: Empty url."); |
| } |
| |
| if (!resolved.is_valid()) { |
| return base::unexpected("SubAppsServiceImpl: Invalid url."); |
| } |
| |
| return base::ok(resolved); |
| } |
| |
| std::string ConvertUrlToPath(const webapps::ManifestId& manifest_id) { |
| return manifest_id.PathForRequest(); |
| } |
| |
| base::expected<std::vector<SubAppInstallParams>, std::string> |
| AddOptionsFromMojo( |
| const url::Origin& origin, |
| const std::vector<SubAppsServiceAddParametersPtr>& sub_apps_to_add_mojo) { |
| std::vector<SubAppInstallParams> sub_apps; |
| for (const auto& sub_app : sub_apps_to_add_mojo) { |
| ASSIGN_OR_RETURN(webapps::ManifestId manifest_id, |
| ConvertPathToUrl(sub_app->manifest_id_path, origin)); |
| ASSIGN_OR_RETURN(GURL install_url, |
| ConvertPathToUrl(sub_app->install_url_path, origin)); |
| sub_apps.emplace_back(std::move(manifest_id), std::move(install_url)); |
| } |
| return sub_apps; |
| } |
| |
| Profile* GetProfile(content::RenderFrameHost& render_frame_host) { |
| return Profile::FromBrowserContext(render_frame_host.GetBrowserContext()); |
| } |
| |
| WebAppProvider* GetWebAppProvider(content::RenderFrameHost& render_frame_host) { |
| auto* const initiator_web_contents = |
| content::WebContents::FromRenderFrameHost(&render_frame_host); |
| auto* provider = WebAppProvider::GetForWebContents(initiator_web_contents); |
| DCHECK(provider); |
| return provider; |
| } |
| |
| const webapps::AppId* GetAppId(content::RenderFrameHost& render_frame_host) { |
| auto* const initiator_web_contents = |
| content::WebContents::FromRenderFrameHost(&render_frame_host); |
| return WebAppTabHelper::GetAppId(initiator_web_contents); |
| } |
| |
| blink::mojom::SubAppsServiceResultCode InstallResultCodeToMojo( |
| webapps::InstallResultCode install_result_code) { |
| return webapps::IsSuccess(install_result_code) |
| ? blink::mojom::SubAppsServiceResultCode::kSuccess |
| : blink::mojom::SubAppsServiceResultCode::kFailure; |
| } |
| |
| void ReturnAllAddsAsFailed( |
| const std::vector<SubAppsServiceAddParametersPtr>& sub_apps, |
| SubAppsServiceImpl::AddCallback result_callback) { |
| std::vector<SubAppsServiceAddResultPtr> result; |
| for (const auto& sub_app : sub_apps) { |
| result.emplace_back(SubAppsServiceAddResult::New( |
| sub_app->manifest_id_path, SubAppsServiceResultCode::kFailure)); |
| } |
| std::move(result_callback).Run(std::move(result)); |
| } |
| |
| bool IsInstalledNonChildApp(content::RenderFrameHost& render_frame_host) { |
| auto* app_id = GetAppId(render_frame_host); |
| if (!app_id) { |
| return false; |
| } |
| |
| auto* provider = GetWebAppProvider(render_frame_host); |
| auto* web_app = provider->registrar_unsafe().GetAppById(*app_id); |
| return (web_app && !web_app->IsSubAppInstalledApp()); |
| } |
| |
| // Verify that the calling app has the SubApps permissions policy set and that |
| // it is an installed IWA that is not a sub app itself. This check is called |
| // from `CreateIfAllowed` and from each of the APIs entry points to avoid a |
| // potential race between the parent app calling an API while being uninstalled. |
| bool CanAccessSubAppsApi(content::RenderFrameHost& render_frame_host) { |
| return render_frame_host.IsFeatureEnabled( |
| network::mojom::PermissionsPolicyFeature::kSubApps) && |
| content::HasIsolatedContextCapability(&render_frame_host) && |
| IsInstalledNonChildApp(render_frame_host); |
| } |
| |
| bool ShouldSkipUserConfirmation(content::RenderFrameHost& frame) { |
| #if BUILDFLAG(IS_CHROMEOS) |
| auto const* profile = Profile::FromBrowserContext(frame.GetBrowserContext()); |
| if (!profile) { |
| return false; |
| } |
| |
| auto const* prefs = profile->GetPrefs(); |
| if (!prefs) { |
| return false; |
| } |
| |
| return policy::IsOriginInAllowlist( |
| frame.GetLastCommittedURL(), prefs, |
| prefs::kSubAppsAPIsAllowedWithoutGestureAndAuthorizationForOrigins); |
| #else // BUILDFLAG(IS_CHROMEOS) |
| return false; |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| } |
| |
| } // namespace |
| |
| SubAppsServiceImpl::SubAppsServiceImpl( |
| content::RenderFrameHost& render_frame_host, |
| mojo::PendingReceiver<SubAppsService> receiver) |
| : DocumentService(render_frame_host, std::move(receiver)) {} |
| |
| SubAppsServiceImpl::~SubAppsServiceImpl() = default; |
| |
| SubAppsServiceImpl::AddCallInfo::AddCallInfo() = default; |
| SubAppsServiceImpl::AddCallInfo::~AddCallInfo() = default; |
| |
| // static |
| void SubAppsServiceImpl::CreateIfAllowed( |
| content::RenderFrameHost* render_frame_host, |
| mojo::PendingReceiver<SubAppsService> receiver) { |
| CHECK(render_frame_host); |
| |
| // This class is created only on the primary main frame. |
| if (!render_frame_host->IsInPrimaryMainFrame()) { |
| receiver.reset(); |
| return; |
| } |
| |
| // Bail if Web Apps aren't enabled on current profile. |
| if (!AreWebAppsEnabled(Profile::FromBrowserContext( |
| content::WebContents::FromRenderFrameHost(render_frame_host) |
| ->GetBrowserContext()))) { |
| receiver.reset(); |
| return; |
| } |
| |
| // Bail if the calling app is not an Isolated Web App or is not installed or |
| // is a sub-app itself. |
| if (!CanAccessSubAppsApi(*render_frame_host)) { |
| receiver.reset(); |
| return; |
| } |
| |
| // The object is bound to the lifetime of `render_frame_host` and the mojo |
| // connection. See DocumentService for details. |
| new SubAppsServiceImpl(*render_frame_host, std::move(receiver)); |
| } |
| |
| void SubAppsServiceImpl::Add( |
| std::vector<SubAppsServiceAddParametersPtr> sub_apps_to_add, |
| AddCallback result_callback) { |
| WebAppProvider* provider = GetWebAppProvider(render_frame_host()); |
| if (!provider->on_registry_ready().is_signaled()) { |
| provider->on_registry_ready().Post( |
| FROM_HERE, |
| base::BindOnce(&SubAppsServiceImpl::Add, weak_ptr_factory_.GetWeakPtr(), |
| std::move(sub_apps_to_add), std::move(result_callback))); |
| return; |
| } |
| |
| if (!CanAccessSubAppsApi(render_frame_host())) { |
| ReturnAllAddsAsFailed(sub_apps_to_add, std::move(result_callback)); |
| return; |
| } |
| |
| if (sub_apps_to_add.empty()) { |
| std::move(result_callback).Run({}); |
| return; |
| } |
| |
| // Check if origin is embargoed because of too many dismissals. |
| if (PermissionDecisionAutoBlockerFactory::GetForProfile( |
| Profile::FromBrowserContext(render_frame_host().GetBrowserContext())) |
| ->IsEmbargoed(render_frame_host().GetLastCommittedOrigin().GetURL(), |
| ContentSettingsType::SUB_APP_INSTALLATION_PROMPTS)) { |
| ReturnAllAddsAsFailed(sub_apps_to_add, std::move(result_callback)); |
| return; |
| } |
| |
| ASSIGN_OR_RETURN( |
| (std::vector<SubAppInstallParams> add_options), |
| AddOptionsFromMojo(render_frame_host().GetLastCommittedOrigin(), |
| sub_apps_to_add), |
| // Compromised renderer, bail immediately (this call deletes *this). |
| &SubAppsServiceImpl::ReportBadMessageAndDeleteThis, this); |
| |
| CHECK(AreWebAppsUserInstallable( |
| Profile::FromBrowserContext(render_frame_host().GetBrowserContext()))); |
| |
| // Assign id to this add call |
| int add_call_id = next_add_call_id_++; |
| AddCallInfo& add_call_info = add_call_info_[add_call_id]; |
| add_call_info.mojo_callback = std::move(result_callback); |
| |
| auto parent_manifest_id = provider->registrar_unsafe() |
| .GetAppById(*GetAppId(render_frame_host())) |
| ->manifest_id(); |
| CollectInstallData(add_call_id, std::move(add_options), parent_manifest_id); |
| } |
| |
| void SubAppsServiceImpl::CollectInstallData( |
| int add_call_id, |
| std::vector<SubAppInstallParams> requested_installs, |
| webapps::ManifestId parent_manifest_id) { |
| WebAppProvider* provider = GetWebAppProvider(render_frame_host()); |
| base::ConcurrentCallbacks< |
| std::pair<webapps::ManifestId, std::unique_ptr<WebAppInstallInfo>>> |
| concurrent; |
| |
| // Schedule data collection for each requested install |
| for (const auto& [manifest_id, url_to_load] : requested_installs) { |
| // Check if app is the parent app itself |
| if (manifest_id == parent_manifest_id) { |
| add_call_info_.at(add_call_id) |
| .results.emplace_back(SubAppsServiceAddResult::New( |
| ConvertUrlToPath(manifest_id), |
| blink::mojom::SubAppsServiceResultCode::kFailure)); |
| continue; |
| } |
| |
| // Check if app is already installed as a sub app |
| if (provider->registrar_unsafe().WasInstalledBySubApp( |
| GenerateAppIdFromManifestId(manifest_id, parent_manifest_id))) { |
| add_call_info_.at(add_call_id) |
| .results.emplace_back(SubAppsServiceAddResult::New( |
| ConvertUrlToPath(manifest_id), |
| blink::mojom::SubAppsServiceResultCode::kSuccess)); |
| continue; |
| } |
| |
| provider->scheduler().FetchInstallInfoFromInstallUrl( |
| manifest_id, url_to_load, parent_manifest_id, |
| base::BindOnce( |
| [](webapps::ManifestId manifest_app_id, |
| std::unique_ptr<WebAppInstallInfo> install_info) { |
| return std::pair(manifest_app_id, std::move(install_info)); |
| }, |
| manifest_id) |
| .Then(concurrent.CreateCallback())); |
| } |
| |
| std::move(concurrent) |
| .Done(base::BindOnce(&SubAppsServiceImpl::ProcessInstallData, |
| weak_ptr_factory_.GetWeakPtr(), add_call_id)); |
| } |
| |
| void SubAppsServiceImpl::ProcessInstallData( |
| int add_call_id, |
| std::vector<std::pair<webapps::ManifestId, |
| std::unique_ptr<WebAppInstallInfo>>> install_data) { |
| AddCallInfo& add_call_info = add_call_info_.at(add_call_id); |
| const webapps::AppId* parent_app_id = GetAppId(render_frame_host()); |
| |
| for (auto& [manifest_id, install_info] : install_data) { |
| // If manifest_id is empty, the app was already installed and no install |
| // info was collected. If it is invalid, do not to trigger an installation |
| // since the command will run into problems. |
| if (!manifest_id.is_valid()) { |
| continue; |
| } |
| |
| if (install_info) { |
| install_info->parent_app_id = *parent_app_id; |
| install_info->user_display_mode = mojom::UserDisplayMode::kStandalone; |
| add_call_info.install_infos.emplace_back(std::move(install_info)); |
| } else { |
| // Log error if install info could not be loaded |
| add_call_info.results.emplace_back(SubAppsServiceAddResult::New( |
| ConvertUrlToPath(manifest_id), |
| blink::mojom::SubAppsServiceResultCode::kFailure)); |
| } |
| } |
| |
| FinishAddCallOrShowInstallDialog(add_call_id); |
| } |
| |
| void SubAppsServiceImpl::FinishAddCallOrShowInstallDialog(int add_call_id) { |
| AddCallInfo& add_call_info = add_call_info_.at(add_call_id); |
| |
| if (add_call_info.install_infos.empty()) { |
| FinishAddCall(add_call_id, {}); |
| return; |
| } |
| |
| if (ShouldSkipUserConfirmation(render_frame_host())) { |
| ProcessDialogResponse(add_call_id, true); |
| return; |
| } |
| |
| WebAppRegistrar& registrar = |
| GetWebAppProvider(render_frame_host())->registrar_unsafe(); |
| const webapps::AppId* parent_app_id = GetAppId(render_frame_host()); |
| |
| add_call_info.install_dialog = |
| std::make_unique<SubAppsInstallDialogController>(); |
| add_call_info.install_dialog->Init( |
| base::BindOnce(&SubAppsServiceImpl::ProcessDialogResponse, |
| weak_ptr_factory_.GetWeakPtr(), add_call_id), |
| add_call_info.install_infos, |
| /*parent_app_name=*/registrar.GetAppShortName(*parent_app_id), |
| *parent_app_id, GetProfile(render_frame_host()), |
| /*window=*/ |
| content::WebContents::FromRenderFrameHost(&render_frame_host()) |
| ->GetTopLevelNativeWindow()); |
| } |
| |
| void SubAppsServiceImpl::ProcessDialogResponse(int add_call_id, |
| bool dialog_accepted) { |
| if (dialog_accepted) { |
| PermissionDecisionAutoBlockerFactory::GetForProfile( |
| Profile::FromBrowserContext(render_frame_host().GetBrowserContext())) |
| ->RemoveEmbargoAndResetCounts( |
| render_frame_host().GetLastCommittedOrigin().GetURL(), |
| ContentSettingsType::SUB_APP_INSTALLATION_PROMPTS); |
| |
| ScheduleSubAppInstalls(add_call_id); |
| return; |
| } |
| |
| // Dialog was declined. |
| PermissionDecisionAutoBlockerFactory::GetForProfile( |
| Profile::FromBrowserContext(render_frame_host().GetBrowserContext())) |
| ->RecordDismissAndEmbargo( |
| render_frame_host().GetLastCommittedOrigin().GetURL(), |
| ContentSettingsType::SUB_APP_INSTALLATION_PROMPTS, |
| /*dismissed_prompt_was_quiet=*/false); |
| |
| AddCallInfo& add_call_info = add_call_info_.at(add_call_id); |
| |
| for (const std::unique_ptr<web_app::WebAppInstallInfo>& install_info : |
| add_call_info.install_infos) { |
| add_call_info.results.emplace_back(SubAppsServiceAddResult::New( |
| ConvertUrlToPath(install_info->manifest_id()), |
| blink::mojom::SubAppsServiceResultCode::kFailure)); |
| } |
| |
| FinishAddCall(add_call_id, {}); |
| } |
| |
| void SubAppsServiceImpl::ScheduleSubAppInstalls(int add_call_id) { |
| AddCallInfo& add_call_info = add_call_info_.at(add_call_id); |
| |
| // Schedule install for each install_info that was collected |
| WebAppProvider* provider = GetWebAppProvider(render_frame_host()); |
| base::ConcurrentCallbacks<SubAppInstallResult> concurrent; |
| for (auto& install_info : add_call_info.install_infos) { |
| webapps::ManifestId manifest_id = install_info->manifest_id(); |
| provider->scheduler().InstallFromInfoWithParams( |
| std::move(install_info), /*overwrite_existing_manifest_fields=*/false, |
| webapps::WebappInstallSource::SUB_APP, |
| base::BindOnce( |
| [](webapps::ManifestId manifest_id, const webapps::AppId& app_id, |
| webapps::InstallResultCode result_code) { |
| return SubAppInstallResult(manifest_id, app_id, result_code); |
| }, |
| manifest_id) |
| .Then(concurrent.CreateCallback()), |
| WebAppInstallParams()); |
| } |
| std::move(concurrent) |
| .Done(base::BindOnce(&SubAppsServiceImpl::FinishAddCall, |
| weak_ptr_factory_.GetWeakPtr(), add_call_id)); |
| } |
| |
| void SubAppsServiceImpl::FinishAddCall( |
| int add_call_id, |
| std::vector<SubAppInstallResult> install_results) { |
| AddCallInfo& add_call_info = add_call_info_.at(add_call_id); |
| |
| for (const auto& [manifest_id, app_id, result_code] : install_results) { |
| add_call_info.results.emplace_back(SubAppsServiceAddResult::New( |
| ConvertUrlToPath(manifest_id), InstallResultCodeToMojo(result_code))); |
| } |
| |
| std::move(add_call_info.mojo_callback).Run(std::move(add_call_info.results)); |
| |
| add_call_info_.erase(add_call_id); |
| } |
| |
| void SubAppsServiceImpl::List(ListCallback result_callback) { |
| WebAppProvider* provider = GetWebAppProvider(render_frame_host()); |
| if (!provider->on_registry_ready().is_signaled()) { |
| provider->on_registry_ready().Post( |
| FROM_HERE, base::BindOnce(&SubAppsServiceImpl::List, |
| weak_ptr_factory_.GetWeakPtr(), |
| std::move(result_callback))); |
| return; |
| } |
| |
| if (!CanAccessSubAppsApi(render_frame_host())) { |
| return std::move(result_callback) |
| .Run(SubAppsServiceListResult::New( |
| SubAppsServiceResultCode::kFailure, |
| std::vector<SubAppsServiceListResultEntryPtr>())); |
| } |
| |
| const WebAppRegistrar& registrar = provider->registrar_unsafe(); |
| std::vector<SubAppsServiceListResultEntryPtr> sub_apps_list; |
| for (const webapps::AppId& sub_app_id : |
| registrar.GetAllSubAppIds(*GetAppId(render_frame_host()))) { |
| webapps::ManifestId manifest_id = registrar.GetAppManifestId(sub_app_id); |
| CHECK(manifest_id.is_valid()); |
| sub_apps_list.push_back(SubAppsServiceListResultEntry::New( |
| ConvertUrlToPath(manifest_id), registrar.GetAppShortName(sub_app_id))); |
| } |
| |
| std::move(result_callback) |
| .Run(SubAppsServiceListResult::New(SubAppsServiceResultCode::kSuccess, |
| std::move(sub_apps_list))); |
| } |
| |
| void SubAppsServiceImpl::Remove( |
| const std::vector<std::string>& manifest_id_paths, |
| RemoveCallback result_callback) { |
| WebAppProvider* provider = GetWebAppProvider(render_frame_host()); |
| if (!provider->on_registry_ready().is_signaled()) { |
| provider->on_registry_ready().Post( |
| FROM_HERE, |
| base::BindOnce(&SubAppsServiceImpl::Remove, |
| weak_ptr_factory_.GetWeakPtr(), manifest_id_paths, |
| std::move(result_callback))); |
| return; |
| } |
| |
| if (!CanAccessSubAppsApi(render_frame_host())) { |
| std::vector<SubAppsServiceRemoveResultPtr> result; |
| for (const std::string& manifest_id_path : manifest_id_paths) { |
| result.emplace_back(SubAppsServiceRemoveResult::New( |
| manifest_id_path, SubAppsServiceResultCode::kFailure)); |
| } |
| |
| return std::move(result_callback).Run(std::move(result)); |
| } |
| |
| // Take weak pointer early as this may get deleted by RemoveSubApp(). |
| base::WeakPtr<SubAppsServiceImpl> weak_ptr = weak_ptr_factory_.GetWeakPtr(); |
| base::ConcurrentCallbacks<SubAppsServiceRemoveResultPtr> concurrent; |
| for (const std::string& manifest_id_path : manifest_id_paths) { |
| RemoveSubApp(manifest_id_path, concurrent.CreateCallback(), |
| GetAppId(render_frame_host())); |
| } |
| std::move(concurrent) |
| .Done(base::BindOnce(&SubAppsServiceImpl::NotifyUninstall, weak_ptr, |
| std::move(result_callback))); |
| } |
| |
| void SubAppsServiceImpl::RemoveSubApp( |
| const std::string& manifest_id_path, |
| base::OnceCallback<void(SubAppsServiceRemoveResultPtr)> callback, |
| const webapps::AppId* calling_app_id) { |
| // Convert `manifest_id_path` from path form to full URL form. |
| ASSIGN_OR_RETURN( |
| const webapps::ManifestId manifest_id, |
| ConvertPathToUrl(manifest_id_path, |
| render_frame_host().GetLastCommittedOrigin()), |
| // Compromised renderer, bail immediately (this call deletes *this). |
| &SubAppsServiceImpl::ReportBadMessageAndDeleteThis, this); |
| |
| WebAppProvider* provider = GetWebAppProvider(render_frame_host()); |
| |
| const webapps::AppId* parent_app_id = GetAppId(render_frame_host()); |
| if (!parent_app_id) { |
| return ReportBadMessageAndDeleteThis("Parent app id is null"); |
| } |
| |
| webapps::ManifestId parent_manifest_id = |
| provider->registrar_unsafe().GetAppManifestId(*parent_app_id); |
| if (!parent_manifest_id.is_valid()) { |
| return ReportBadMessageAndDeleteThis("Parent manifest is null"); |
| } |
| |
| webapps::AppId sub_app_id = |
| GenerateAppIdFromManifestId(manifest_id, parent_manifest_id); |
| const WebApp* app = provider->registrar_unsafe().GetAppById(sub_app_id); |
| |
| // Verify that the app we're trying to remove exists, is installed and that |
| // its parent_app is the one doing the current call. |
| if (!app || !app->parent_app_id() || |
| *calling_app_id != *app->parent_app_id() || |
| !provider->registrar_unsafe().IsInRegistrar(sub_app_id)) { |
| return std::move(callback).Run(SubAppsServiceRemoveResult::New( |
| manifest_id_path, SubAppsServiceResultCode::kFailure)); |
| } |
| |
| provider->scheduler().RemoveInstallManagementMaybeUninstall( |
| sub_app_id, WebAppManagement::Type::kSubApp, |
| webapps::WebappUninstallSource::kSubApp, |
| base::BindOnce( |
| [](std::string manifest_id_path, |
| webapps::UninstallResultCode result_code) { |
| SubAppsServiceResultCode result = |
| webapps::UninstallSucceeded(result_code) |
| ? SubAppsServiceResultCode::kSuccess |
| : SubAppsServiceResultCode::kFailure; |
| return SubAppsServiceRemoveResult::New(manifest_id_path, result); |
| }, |
| manifest_id_path) |
| .Then(std::move(callback))); |
| } |
| |
| void SubAppsServiceImpl::NotifyUninstall( |
| RemoveCallback result_callback, |
| std::vector<SubAppsServiceRemoveResultPtr> remove_results) { |
| int num_successful_uninstalls = std::ranges::count( |
| remove_results, SubAppsServiceResultCode::kSuccess, |
| [](const auto& result) { return result->result_code; }); |
| |
| // If any apps were uninstalled, notify the user. |
| if (num_successful_uninstalls > 0) { |
| WebAppRegistrar& registrar = |
| GetWebAppProvider(render_frame_host())->registrar_unsafe(); |
| const webapps::AppId* parent_app_id = GetAppId(render_frame_host()); |
| const std::u16string parent_app_name = |
| base::UTF8ToUTF16(registrar.GetAppShortName(*parent_app_id)); |
| const GURL start_url = registrar.GetAppStartUrl(*parent_app_id); |
| const std::u16string title = |
| base::i18n::MessageFormatter::FormatWithNamedArgs( |
| l10n_util::GetStringUTF16( |
| IDS_SUB_APPS_UNINSTALL_NOTIFICATION_TITLE), |
| /*name0=*/"NUM_SUB_APPS", num_successful_uninstalls, |
| /*name1=*/"APP_NAME", parent_app_name); |
| const std::u16string message = |
| base::i18n::MessageFormatter::FormatWithNamedArgs( |
| l10n_util::GetStringUTF16( |
| IDS_SUB_APPS_UNINSTALL_NOTIFICATION_DESCRIPTION), |
| /*name0=*/"APP_NAME", parent_app_name); |
| |
| message_center::Notification notification( |
| message_center::NOTIFICATION_TYPE_SIMPLE, |
| kSubAppsUninstallNotificationId, title, message, ui::ImageModel(), |
| /*display_source=*/std::u16string(), |
| /*origin_url=*/start_url, |
| #if BUILDFLAG(IS_CHROMEOS) |
| message_center::NotifierId( |
| message_center::NotifierType::SYSTEM_COMPONENT, |
| kSubAppsUninstallNotifierId, |
| ash::NotificationCatalogName::kSubAppsUninstall), |
| #else |
| message_center::NotifierId( |
| message_center::NotifierType::SYSTEM_COMPONENT, |
| kSubAppsUninstallNotifierId), |
| #endif |
| message_center::RichNotificationData(), |
| /*delegate=*/nullptr); |
| notification.SetSystemPriority(); |
| |
| NotificationDisplayServiceFactory::GetForProfile( |
| GetProfile(render_frame_host())) |
| ->Display(NotificationHandler::Type::WEB_PERSISTENT, notification, |
| /*metadata=*/nullptr); |
| } |
| |
| std::move(result_callback).Run(std::move(remove_results)); |
| } |
| |
| } // namespace web_app |