blob: 3c75afd69affc6be2a7f70fccfe7ec6caa157e43 [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// 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 "base/bind.h"
#include "base/check.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/web_applications/commands/sub_app_install_command.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/browser/web_applications/web_app_command_manager.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_manager.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_utils.h"
#include "components/webapps/browser/install_result_code.h"
#include "components/webapps/browser/installable/installable_metrics.h"
#include "components/webapps/browser/uninstall_result_code.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "url/gurl.h"
using blink::mojom::SubAppsServiceListResult;
using blink::mojom::SubAppsServiceResult;
namespace web_app {
namespace {
std::vector<blink::mojom::SubAppsServiceAddResultPtr> ResultsToMojo(
std::vector<
std::pair<UnhashedAppId, blink::mojom::SubAppsServiceAddResultCode>>
sub_apps_idl) {
std::vector<blink::mojom::SubAppsServiceAddResultPtr> subapps;
for (const auto& [app_id, install_result_code] : sub_apps_idl) {
blink::mojom::SubAppsServiceAddResultPtr mojom_pair =
blink::mojom::SubAppsServiceAddResult::New();
mojom_pair->unhashed_app_id = app_id;
mojom_pair->result_code = install_result_code;
subapps.push_back(std::move(mojom_pair));
}
return subapps;
}
std::vector<std::pair<UnhashedAppId, GURL>> InstallParamsFromMojo(
std::vector<blink::mojom::SubAppsServiceAddInfoPtr> sub_apps_mojo) {
std::vector<std::pair<UnhashedAppId, GURL>> subapps;
for (const auto& pair : sub_apps_mojo) {
subapps.emplace_back(UnhashedAppId(pair->unhashed_app_id),
pair->install_url);
}
return subapps;
}
WebAppProvider* GetWebAppProvider(content::RenderFrameHost& render_frame_host) {
auto* const initiator_web_contents =
content::WebContents::FromRenderFrameHost(&render_frame_host);
return web_app::WebAppProvider::GetForWebContents(initiator_web_contents);
}
absl::optional<AppId> GetAppIdForLastCommittedURL(
content::RenderFrameHost& render_frame_host) {
GURL parent_url = render_frame_host.GetLastCommittedURL();
WebAppProvider* provider = GetWebAppProvider(render_frame_host);
DCHECK(provider);
WebAppRegistrar& web_app_registrar = provider->registrar();
return web_app_registrar.FindAppWithUrlInScope(parent_url);
}
void OnAdd(
SubAppsServiceImpl::AddCallback result_callback,
std::vector<std::pair<UnhashedAppId,
blink::mojom::SubAppsServiceAddResultCode>> results) {
std::move(result_callback).Run(ResultsToMojo(std::move(results)));
}
void OnRemove(SubAppsServiceImpl::RemoveCallback result_callback,
webapps::UninstallResultCode code) {
std::move(result_callback)
.Run(code == webapps::UninstallResultCode::kSuccess
? SubAppsServiceResult::kSuccess
: SubAppsServiceResult::kFailure);
}
} // namespace
SubAppsServiceImpl::SubAppsServiceImpl(
content::RenderFrameHost& render_frame_host,
mojo::PendingReceiver<blink::mojom::SubAppsService> receiver)
: DocumentService(render_frame_host, std::move(receiver)) {}
SubAppsServiceImpl::~SubAppsServiceImpl() = default;
// static
void SubAppsServiceImpl::CreateIfAllowed(
content::RenderFrameHost* render_frame_host,
mojo::PendingReceiver<blink::mojom::SubAppsService> receiver) {
CHECK(render_frame_host);
// This class is created only on the primary main frame (this excludes
// fenced frames and prerendered pages).
DCHECK(render_frame_host->IsInPrimaryMainFrame());
// Bail if Web Apps aren't enabled on current profile.
if (!AreWebAppsEnabled(Profile::FromBrowserContext(
content::WebContents::FromRenderFrameHost(render_frame_host)
->GetBrowserContext()))) {
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<blink::mojom::SubAppsServiceAddInfoPtr> sub_apps,
AddCallback result_callback) {
WebAppProvider* provider = GetWebAppProvider(render_frame_host());
DCHECK(provider);
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), std::move(result_callback)));
return;
}
absl::optional<AppId> parent_app_id =
GetAppIdForLastCommittedURL(render_frame_host());
// Verify that the calling app is installed itself. This check is done here
// and not in `CreateIfAllowed` because of a potential race between doing the
// check there and then running the current function, and the parent app being
// installed/uninstalled.
if (!parent_app_id) {
std::move(result_callback).Run(/*mojom_results=*/{});
return;
}
const GURL& parent_app_url = render_frame_host().GetLastCommittedURL();
// Check that each sub app's install url has the same origin as the parent
// app and that the unhashed app id is a valid URL.
for (const blink::mojom::SubAppsServiceAddInfoPtr& sub_app : sub_apps) {
GURL sub_app_install_url(sub_app->install_url);
if (!url::IsSameOriginWith(sub_app_install_url, parent_app_url)) {
std::move(result_callback).Run(/*mojom_results=*/{});
ReportBadMessageAndDeleteThis(
"Unexpected request: Add calls only supported for sub apps on the "
"same origin as the calling app.");
return;
}
if (!GURL(sub_app->unhashed_app_id).is_valid()) {
std::move(result_callback).Run(/*mojom_results=*/{});
ReportBadMessageAndDeleteThis("App ids must be valid URLs.");
return;
}
}
std::vector<AppId> app_ids_vector = {*parent_app_id};
base::ranges::for_each(
sub_apps,
[&app_ids_vector](const blink::mojom::SubAppsServiceAddInfoPtr& sub_app) {
app_ids_vector.push_back(
GenerateAppIdFromUnhashed(sub_app->unhashed_app_id));
});
base::flat_set<AppId> app_ids =
base::flat_set<AppId>(std::move(app_ids_vector));
auto install_command = std::make_unique<SubAppInstallCommand>(
&provider->install_manager(), &provider->registrar(), *parent_app_id,
InstallParamsFromMojo(std::move(sub_apps)), std::move(app_ids),
base::BindOnce(&OnAdd, std::move(result_callback)));
provider->command_manager().ScheduleCommand(std::move(install_command));
}
void SubAppsServiceImpl::List(ListCallback result_callback) {
// Verify that the calling app is installed itself (cf. `Add`).
absl::optional<AppId> parent_app_id =
GetAppIdForLastCommittedURL(render_frame_host());
if (!parent_app_id.has_value()) {
return std::move(result_callback)
.Run(SubAppsServiceListResult::New(SubAppsServiceResult::kFailure,
std::vector<UnhashedAppId>()));
}
WebAppProvider* provider = GetWebAppProvider(render_frame_host());
DCHECK(provider);
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;
}
WebAppRegistrar& registrar = provider->registrar();
std::vector<UnhashedAppId> sub_app_ids;
for (const AppId& web_app_id : registrar.GetAllSubAppIds(*parent_app_id)) {
const WebApp* web_app = registrar.GetAppById(web_app_id);
sub_app_ids.push_back(
GenerateAppIdUnhashed(web_app->manifest_id(), web_app->start_url()));
}
std::move(result_callback)
.Run(SubAppsServiceListResult::New(SubAppsServiceResult::kSuccess,
std::move(sub_app_ids)));
}
void SubAppsServiceImpl::Remove(const UnhashedAppId& unhashed_app_id,
RemoveCallback result_callback) {
WebAppProvider* provider = GetWebAppProvider(render_frame_host());
DCHECK(provider);
if (!provider->on_registry_ready().is_signaled()) {
provider->on_registry_ready().Post(
FROM_HERE, base::BindOnce(&SubAppsServiceImpl::Remove,
weak_ptr_factory_.GetWeakPtr(),
unhashed_app_id, std::move(result_callback)));
return;
}
// Verify that the calling app is installed itself (cf. `Add`).
absl::optional<AppId> calling_app_id =
GetAppIdForLastCommittedURL(render_frame_host());
if (!calling_app_id.has_value()) {
return std::move(result_callback).Run(SubAppsServiceResult::kFailure);
}
// `unhashed_app_id` should form a proper URL
// (https://www.w3.org/TR/appmanifest/#dfn-identity).
if (!GURL(unhashed_app_id).is_valid()) {
return std::move(result_callback).Run(SubAppsServiceResult::kFailure);
}
AppId sub_app_id = GenerateAppIdFromUnhashed(unhashed_app_id);
const WebApp* app = provider->registrar().GetAppById(sub_app_id);
// Verify that the app we're trying to remove exists, that its parent_app is
// the one doing the current call, and that the app was locally installed.
if (!app || calling_app_id != app->parent_app_id() ||
!app->is_locally_installed()) {
return std::move(result_callback).Run(SubAppsServiceResult::kFailure);
}
provider->install_finalizer().UninstallExternalWebApp(
sub_app_id, WebAppManagement::Type::kSubApp,
webapps::WebappUninstallSource::kSubApp,
base::BindOnce(&OnRemove, std::move(result_callback)));
}
} // namespace web_app