blob: db4f2d0db20b3c3fee81272bf264781449dec502 [file] [log] [blame]
// 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 "base/barrier_callback.h"
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/types/expected.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_id.h"
#include "chrome/browser/web_applications/web_app_install_finalizer.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/browser/web_applications/web_contents/web_app_data_retriever.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"
#include "url/origin.h"
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 {
namespace {
// 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<std::string, 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.");
}
return base::ok(resolved.spec());
}
std::string ConvertUrlToPath(const UnhashedAppId& unhashed_app_id) {
return GURL(unhashed_app_id).PathForRequest();
}
base::expected<std::vector<std::pair<UnhashedAppId, GURL>>, std::string>
AddOptionsFromMojo(
const url::Origin& origin,
const std::vector<SubAppsServiceAddParametersPtr>& sub_apps_to_add_mojo) {
std::vector<std::pair<UnhashedAppId, GURL>> sub_apps;
for (const auto& sub_app : sub_apps_to_add_mojo) {
base::expected<std::string, std::string> unhashed_app_id =
ConvertPathToUrl(sub_app->unhashed_app_id_path, origin);
if (!unhashed_app_id.has_value()) {
return base::unexpected(unhashed_app_id.error());
}
base::expected<std::string, std::string> install_url =
ConvertPathToUrl(sub_app->install_url_path, origin);
if (!install_url.has_value()) {
return base::unexpected(install_url.error());
}
sub_apps.emplace_back(unhashed_app_id.value(), install_url.value());
}
return sub_apps;
}
SubAppsServiceImpl::AddResultsMojo AddResultsToMojo(
const SubAppsServiceImpl::AddResults& add_results) {
SubAppsServiceImpl::AddResultsMojo add_results_mojo;
for (const auto& [unhashed_app_id, result_code] : add_results) {
add_results_mojo.emplace_back(SubAppsServiceAddResult::New(
ConvertUrlToPath(unhashed_app_id), result_code));
}
return add_results_mojo;
}
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 AppId* GetAppId(content::RenderFrameHost& render_frame_host) {
auto* const initiator_web_contents =
content::WebContents::FromRenderFrameHost(&render_frame_host);
return WebAppTabHelper::GetAppId(initiator_web_contents);
}
void OnAdd(SubAppsServiceImpl::AddCallback result_callback,
SubAppsServiceImpl::AddResults results) {
std::move(result_callback).Run(AddResultsToMojo(results));
}
} // namespace
SubAppsServiceImpl::SubAppsServiceImpl(
content::RenderFrameHost& render_frame_host,
mojo::PendingReceiver<SubAppsService> receiver)
: DocumentService(render_frame_host, std::move(receiver)) {}
SubAppsServiceImpl::~SubAppsServiceImpl() = 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()) {
return;
}
// 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<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;
}
const AppId* parent_app_id = GetAppId(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::vector<SubAppsServiceAddResultPtr> result;
for (const auto& sub_app : sub_apps_to_add) {
result.emplace_back(SubAppsServiceAddResult::New(
sub_app->unhashed_app_id_path, SubAppsServiceResultCode::kFailure));
}
return std::move(result_callback).Run(/*mojom_results=*/std::move(result));
}
base::expected<std::vector<std::pair<UnhashedAppId, GURL>>, std::string>
add_options = AddOptionsFromMojo(
render_frame_host().GetLastCommittedOrigin(), sub_apps_to_add);
if (!add_options.has_value()) {
// Compromised renderer, bail immediately (this call deletes *this).
return ReportBadMessageAndDeleteThis(add_options.error());
}
auto install_command = std::make_unique<SubAppInstallCommand>(
*parent_app_id, add_options.value(),
base::BindOnce(&OnAdd, std::move(result_callback)),
Profile::FromBrowserContext(render_frame_host().GetBrowserContext()),
std::make_unique<WebAppUrlLoader>(),
std::make_unique<WebAppDataRetriever>());
provider->command_manager().ScheduleCommand(std::move(install_command));
}
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;
}
// Verify that the calling app is installed itself (cf. `Add`).
const AppId* parent_app_id = GetAppId(render_frame_host());
if (!parent_app_id) {
return std::move(result_callback)
.Run(SubAppsServiceListResult::New(
SubAppsServiceResultCode::kFailure,
std::vector<SubAppsServiceListResultEntryPtr>()));
}
WebAppRegistrar& registrar = provider->registrar_unsafe();
std::vector<SubAppsServiceListResultEntryPtr> sub_apps_list;
for (const AppId& sub_app_id : registrar.GetAllSubAppIds(*parent_app_id)) {
const WebApp* sub_app = registrar.GetAppById(sub_app_id);
UnhashedAppId unhashed_app_id =
GenerateAppIdUnhashed(sub_app->manifest_id(), sub_app->start_url());
sub_apps_list.push_back(SubAppsServiceListResultEntry::New(
ConvertUrlToPath(unhashed_app_id), sub_app->untranslated_name()));
}
std::move(result_callback)
.Run(SubAppsServiceListResult::New(SubAppsServiceResultCode::kSuccess,
std::move(sub_apps_list)));
}
void SubAppsServiceImpl::Remove(
const std::vector<std::string>& unhashed_app_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(), unhashed_app_id_paths,
std::move(result_callback)));
return;
}
// Verify that the calling app is installed itself (cf. `Add`).
const AppId* calling_app_id = GetAppId(render_frame_host());
if (!calling_app_id) {
std::vector<SubAppsServiceRemoveResultPtr> result;
for (const std::string& unhashed_app_id_path : unhashed_app_id_paths) {
result.emplace_back(SubAppsServiceRemoveResult::New(
unhashed_app_id_path, SubAppsServiceResultCode::kFailure));
}
return std::move(result_callback).Run(std::move(result));
}
auto remove_barrier_callback =
base::BarrierCallback<SubAppsServiceRemoveResultPtr>(
unhashed_app_id_paths.size(), std::move(result_callback));
for (const std::string& unhashed_app_id_path : unhashed_app_id_paths) {
RemoveSubApp(unhashed_app_id_path, remove_barrier_callback, calling_app_id);
}
}
void SubAppsServiceImpl::RemoveSubApp(
const std::string& unhashed_app_id_path,
base::OnceCallback<void(SubAppsServiceRemoveResultPtr)> callback,
const AppId* calling_app_id) {
// Convert `unhashed_app_id_path` from path form to full URL form.
base::expected<std::string, std::string> unhashed_app_id_with_error =
ConvertPathToUrl(unhashed_app_id_path,
render_frame_host().GetLastCommittedOrigin());
if (!unhashed_app_id_with_error.has_value()) {
// Compromised renderer, bail immediately (this call deletes *this).
return ReportBadMessageAndDeleteThis(unhashed_app_id_with_error.error());
}
const UnhashedAppId unhashed_app_id = unhashed_app_id_with_error.value();
AppId sub_app_id = GenerateAppIdFromUnhashed(unhashed_app_id);
WebAppProvider* provider = GetWebAppProvider(render_frame_host());
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().IsInstalled(sub_app_id)) {
return std::move(callback).Run(SubAppsServiceRemoveResult::New(
unhashed_app_id_path, SubAppsServiceResultCode::kFailure));
}
provider->install_finalizer().UninstallExternalWebApp(
sub_app_id, WebAppManagement::Type::kSubApp,
webapps::WebappUninstallSource::kSubApp,
base::BindOnce(
[](std::string unhashed_app_id_path,
webapps::UninstallResultCode result_code) {
SubAppsServiceResultCode result =
result_code == webapps::UninstallResultCode::kSuccess
? SubAppsServiceResultCode::kSuccess
: SubAppsServiceResultCode::kFailure;
return SubAppsServiceRemoveResult::New(unhashed_app_id_path,
result);
},
unhashed_app_id_path)
.Then(std::move(callback)));
}
} // namespace web_app