blob: 850a2a1b1c71358a5bd543d56e687103747626e4 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <climits>
#include <initializer_list>
#include <memory>
#include <string>
#include <string_view>
#include <tuple>
#include <vector>
#include "base/check.h"
#include "base/functional/function_ref.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/devtools/protocol/devtools_protocol_test_support.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/web_applications/isolated_web_apps/test/isolated_web_app_builder.h"
#include "chrome/browser/web_applications/proto/web_app_os_integration_state.pb.h"
#include "chrome/browser/web_applications/test/os_integration_test_override_impl.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/browser/web_applications/web_app.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/common/chrome_features.h"
#include "chrome/common/url_constants.h"
#include "components/web_package/signed_web_bundles/signed_web_bundle_id.h"
#include "components/webapps/common/web_app_id.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "net/base/filename_util.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "url/gurl.h"
namespace {
using web_app::WebAppInstallInfo;
using web_app::WebAppProvider;
using webapps::ManifestId;
class IWAProtocolTestBase : public DevToolsProtocolTestBase {
public:
IWAProtocolTestBase() {
iwa_scoped_feature_list_.InitWithFeatures(
{
#if !BUILDFLAG(IS_CHROMEOS)
features::kIsolatedWebApps,
#endif // !BUILDFLAG(IS_CHROMEOS)
features::kIsolatedWebAppDevMode},
{});
}
void SetUp() override {
auto [url, bundle_id] = SetUpIwa();
bundle_url_ = url;
bundle_id_ = bundle_id;
auto url_info_ =
web_app::IsolatedWebAppUrlInfo::CreateFromSignedWebBundleId(bundle_id);
app_id_ = url_info_.app_id();
// This is strange, but the tests are running in the SetUp().
// So all additional setup must be done before.
DevToolsProtocolTestBase::SetUp();
}
virtual std::pair<GURL, web_package::SignedWebBundleId> SetUpIwa() = 0;
void SetUpOnMainThread() override {
DevToolsProtocolTestBase::SetUpOnMainThread();
AttachToBrowserTarget();
override_registration_ =
web_app::OsIntegrationTestOverrideImpl::OverrideForTesting();
}
void TearDownOnMainThread() override {
web_app::test::UninstallAllWebApps(browser()->profile());
override_registration_.reset();
DevToolsProtocolTestBase::TearDownOnMainThread();
}
protected:
GURL InstallUrl() const { return bundle_url_; }
std::string InstallManifestId() const {
return base::StrCat({chrome::kIsolatedAppScheme, "://", bundle_id_.id()});
}
webapps::AppId AppId() const { return app_id_; }
bool AppExists() {
auto* provider = WebAppProvider::GetForTest(browser()->profile());
CHECK(provider);
return provider->registrar_unsafe().IsInRegistrar(AppId());
}
void InstallCommand(const GURL& url) {
EXPECT_TRUE(SendCommandSync("PWA.install",
base::Value::Dict{}
.Set("manifestId", InstallManifestId())
.Set("installUrlOrBundleUrl", url.spec())));
EXPECT_TRUE(AppExists());
if (error()) {
const std::string& message = *error()->FindString("message");
LOG(ERROR) << message;
}
}
void Install() { InstallCommand(bundle_url_); }
void AssertErrorMessageContains(
std::initializer_list<std::string> pieces) const {
ASSERT_TRUE(error());
const std::string& message = *error()->FindString("message");
for (const auto& piece : pieces) {
EXPECT_THAT(message, testing::HasSubstr(piece));
}
}
private:
std::unique_ptr<web_app::OsIntegrationTestOverrideImpl::BlockingRegistration>
override_registration_;
base::test::ScopedFeatureList iwa_scoped_feature_list_;
GURL bundle_url_;
webapps::AppId app_id_;
// Will be overwritten.
web_package::SignedWebBundleId bundle_id_ =
web_package::SignedWebBundleId::CreateRandomForProxyMode();
};
class IWAProtocolTestLocalFile : public IWAProtocolTestBase {
std::pair<GURL, web_package::SignedWebBundleId> SetUpIwa() override {
bundle_ = web_app::IsolatedWebAppBuilder(web_app::ManifestBuilder())
.BuildBundle();
return std::pair(net::FilePathToFileURL(bundle_->path()),
bundle_->web_bundle_id());
}
private:
std::unique_ptr<web_app::ScopedBundledIsolatedWebApp> bundle_;
};
class IWAProtocolTestRemoteFile : public IWAProtocolTestBase {
std::pair<GURL, web_package::SignedWebBundleId> SetUpIwa() override {
bundle_ = web_app::IsolatedWebAppBuilder(web_app::ManifestBuilder())
.BuildBundle();
const base::FilePath& bundle_path = bundle_->path();
embedded_test_server()->AddDefaultHandlers();
embedded_test_server()->ServeFilesFromDirectory(bundle_path.DirName());
test_server_closer_ = embedded_test_server()->StartAndReturnHandle();
GURL url = embedded_test_server()->GetURL(
"/" + bundle_path.BaseName().MaybeAsASCII());
return std::pair(url, bundle_->web_bundle_id());
}
private:
net::test_server::EmbeddedTestServerHandle test_server_closer_;
std::unique_ptr<web_app::ScopedBundledIsolatedWebApp> bundle_;
};
class IWAProtocolTestRemoteProxy : public IWAProtocolTestBase {
std::pair<GURL, web_package::SignedWebBundleId> SetUpIwa() override {
proxy_app_ = web_app::IsolatedWebAppBuilder(web_app::ManifestBuilder())
.BuildAndStartProxyServer();
auto bundle_id = web_package::SignedWebBundleId::CreateRandomForProxyMode();
return std::pair(proxy_app_->proxy_server().GetOrigin().GetURL(),
bundle_id);
}
private:
std::unique_ptr<web_app::ScopedProxyIsolatedWebApp> proxy_app_;
};
} // namespace
IN_PROC_BROWSER_TEST_F(IWAProtocolTestLocalFile, Install) {
Install();
}
IN_PROC_BROWSER_TEST_F(IWAProtocolTestRemoteFile, Install) {
Install();
}
IN_PROC_BROWSER_TEST_F(IWAProtocolTestRemoteProxy, Install) {
Install();
}
// In comparison with PWA, we cannot install IWA twice.
IN_PROC_BROWSER_TEST_F(IWAProtocolTestLocalFile, Install_Twice) {
Install();
ASSERT_FALSE(SendCommandSync(
"PWA.install", base::Value::Dict{}
.Set("manifestId", InstallManifestId())
.Set("installUrlOrBundleUrl", InstallUrl().spec())));
AssertErrorMessageContains({InstallManifestId(), "already installed"});
ASSERT_TRUE(AppExists());
}
IN_PROC_BROWSER_TEST_F(IWAProtocolTestLocalFile, Install_UrlUnreachable) {
ASSERT_FALSE(SendCommandSync(
"PWA.install",
base::Value::Dict{}
.Set("manifestId", InstallManifestId())
.Set("installUrlOrBundleUrl", "http://hello/this/is/not/existing")));
AssertErrorMessageContains(
{InstallManifestId(), "http://hello/this/is/not/existing"});
ASSERT_FALSE(AppExists());
}
IN_PROC_BROWSER_TEST_F(IWAProtocolTestLocalFile, Install_InvalidBundleId) {
std::string garbage_id = "isolated-app://garbage_id";
ASSERT_FALSE(SendCommandSync(
"PWA.install", base::Value::Dict{}
.Set("manifestId", garbage_id)
.Set("installUrlOrBundleUrl", InstallUrl().spec())));
AssertErrorMessageContains(
{"must be a valid signed web bundle id", garbage_id});
ASSERT_FALSE(AppExists());
}
IN_PROC_BROWSER_TEST_F(IWAProtocolTestLocalFile, Install_UnmatchManifestId) {
std::string unmatched_id =
"isolated-app://"
"aiv4bxauvcu3zvbu6r5yynoh5atkzqqaoeof5mwz54b4zfywcrjuoaacai";
ASSERT_FALSE(SendCommandSync(
"PWA.install", base::Value::Dict{}
.Set("manifestId", unmatched_id)
.Set("installUrlOrBundleUrl", InstallUrl().spec())));
AssertErrorMessageContains(
{InstallUrl().spec(), unmatched_id, "Web bundle id mismatch"});
ASSERT_FALSE(AppExists());
}
IN_PROC_BROWSER_TEST_F(IWAProtocolTestRemoteFile, Install_UnmatchManifestId) {
std::string unmatched_id =
"isolated-app://"
"aiv4bxauvcu3zvbu6r5yynoh5atkzqqaoeof5mwz54b4zfywcrjuoaacai";
ASSERT_FALSE(SendCommandSync(
"PWA.install", base::Value::Dict{}
.Set("manifestId", unmatched_id)
.Set("installUrlOrBundleUrl", InstallUrl().spec())));
AssertErrorMessageContains(
{InstallUrl().spec(), unmatched_id, "Web bundle id mismatch"});
ASSERT_FALSE(AppExists());
}
IN_PROC_BROWSER_TEST_F(IWAProtocolTestLocalFile, Install_Uninstall) {
ASSERT_FALSE(AppExists());
Install();
ASSERT_TRUE(AppExists());
ASSERT_TRUE(SendCommandSync(
"PWA.uninstall",
base::Value::Dict{}.Set("manifestId", InstallManifestId())));
ASSERT_FALSE(AppExists());
}