blob: 59e1d4e08d1c21f632cd1d32599d14a5fd0b3c65 [file] [log] [blame]
// Copyright 2023 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/commands/external_app_resolution_command.h"
#include <memory>
#include <string>
#include <tuple>
#include "base/containers/contains.h"
#include "base/containers/flat_map.h"
#include "base/containers/to_vector.h"
#include "base/functional/callback_helpers.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/test_future.h"
#include "chrome/browser/web_applications/commands/internal/callback_command.h"
#include "chrome/browser/web_applications/external_install_options.h"
#include "chrome/browser/web_applications/locks/app_lock.h"
#include "chrome/browser/web_applications/mojom/user_display_mode.mojom.h"
#include "chrome/browser/web_applications/test/fake_data_retriever.h"
#include "chrome/browser/web_applications/test/fake_os_integration_manager.h"
#include "chrome/browser/web_applications/test/fake_web_app_provider.h"
#include "chrome/browser/web_applications/test/fake_web_app_ui_manager.h"
#include "chrome/browser/web_applications/test/fake_web_contents_manager.h"
#include "chrome/browser/web_applications/test/test_file_utils.h"
#include "chrome/browser/web_applications/test/web_app_icon_test_utils.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/browser/web_applications/test/web_app_test.h"
#include "chrome/browser/web_applications/web_app_command_manager.h"
#include "chrome/browser/web_applications/web_app_constants.h"
#include "chrome/browser/web_applications/web_app_helpers.h"
#include "chrome/browser/web_applications/web_app_icon_generator.h"
#include "chrome/browser/web_applications/web_app_icon_manager.h"
#include "chrome/browser/web_applications/web_app_install_info.h"
#include "chrome/browser/web_applications/web_app_install_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/services/app_service/public/cpp/icon_info.h"
#include "components/webapps/browser/install_result_code.h"
#include "components/webapps/browser/installable/installable_logging.h"
#include "components/webapps/browser/web_contents/web_app_url_loader.h"
#include "components/webapps/common/web_app_id.h"
#include "net/http/http_status_code.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/gfx/geometry/size.h"
using testing::_;
using testing::Return;
namespace web_app {
namespace {
struct PageStateOptions {
bool empty_web_app_info = false;
webapps::WebAppUrlLoaderResult url_load_result =
webapps::WebAppUrlLoaderResult::kUrlLoaded;
std::optional<GURL> manifest_id;
};
class MockWebAppUiManager : public web_app::FakeWebAppUiManager {
public:
MOCK_METHOD(void,
NotifyAppRelaunchState,
(const webapps::AppId& placeholder_app_id,
const webapps::AppId& final_app_id,
const std::u16string& final_app_name,
base::WeakPtr<Profile> profile,
AppRelaunchState relaunch_state),
(override));
MOCK_METHOD(size_t,
GetNumWindowsForApp,
(const webapps::AppId& app_id),
(override));
};
class ExternalAppResolutionCommandTest : public WebAppTest {
public:
const GURL kWebAppUrl = GURL("https://example.com/path/index.html");
const GURL kWebAppScope = GURL("https://example.com/path/");
const webapps::AppId kWebAppId =
GenerateAppId(/*manifest_id_path=*/std::nullopt, kWebAppUrl);
const GURL kWebAppManifestUrl =
GURL("https://example.com/path/manifest.json");
using BitmapData = std::map<SquareSizePx, SkBitmap>;
ExternallyManagedAppManager::InstallResult InstallAndWait(
const ExternalInstallOptions& install_options,
std::unique_ptr<WebAppDataRetriever> data_retriever = nullptr) {
base::test::TestFuture<ExternallyManagedAppManager::InstallResult> future;
webapps::AppId placeholder_app_id =
GenerateAppId(std::nullopt, install_options.install_url);
const bool is_placeholder_installed = registrar().IsPlaceholderApp(
placeholder_app_id,
ConvertExternalInstallSourceToSource(install_options.install_source));
std::unique_ptr<ExternalAppResolutionCommand> command =
std::make_unique<ExternalAppResolutionCommand>(
*profile(), install_options,
is_placeholder_installed
? std::optional<webapps::AppId>(placeholder_app_id)
: std::nullopt,
future.GetCallback());
if (data_retriever) {
command->SetDataRetrieverForTesting(std::move(data_retriever));
}
provider()->command_manager().ScheduleCommand(std::move(command));
return future.Get<0>();
}
blink::mojom::ManifestPtr CreateValidManifest() {
blink::mojom::ManifestPtr manifest = blink::mojom::Manifest::New();
manifest->name = u"Example App";
manifest->short_name = u"App";
manifest->start_url = kWebAppUrl;
manifest->id = GenerateManifestIdFromStartUrlOnly(kWebAppUrl);
manifest->display = blink::mojom::DisplayMode::kStandalone;
return manifest;
}
void SetUp() override {
WebAppTest::SetUp();
auto shortcut_manager = std::make_unique<TestShortcutManager>(profile());
shortcut_manager_ = shortcut_manager.get();
FakeWebAppProvider::Get(profile())
->GetOsIntegrationManager()
.AsTestOsIntegrationManager()
->SetShortcutManager(std::move(shortcut_manager));
auto ui_manager = std::make_unique<MockWebAppUiManager>();
ui_manager_ = ui_manager.get();
web_app::FakeWebAppProvider::Get(profile())->SetWebAppUiManager(
std::move(ui_manager));
test::AwaitStartWebAppProviderAndSubsystems(profile());
}
void TearDown() override {
shortcut_manager_ = nullptr;
ui_manager_ = nullptr;
WebAppTest::TearDown();
}
apps::IconInfo GetMockIconInfo(const SquareSizePx& sq_size) {
apps::IconInfo info;
const GURL url("https://example.com/");
info.url = url.Resolve("icon_any" + base::NumberToString(sq_size) + ".png");
info.square_size_px = sq_size;
return info;
}
IconsMap CreateAndLoadIconMapFromSizes(
const std::vector<SquareSizePx>& sizes_px,
const std::vector<SkColor>& colors,
blink::mojom::Manifest* manifest) {
DCHECK_EQ(colors.size(), sizes_px.size());
IconsMap icons_map;
const GURL url = GURL("https://example.com/");
for (size_t i = 0; i < sizes_px.size(); i++) {
GURL icon_url =
url.Resolve("icon_any" + base::NumberToString(sizes_px[i]) + ".png");
manifest->icons.push_back(
CreateSquareImageResource(icon_url, sizes_px[i], {IconPurpose::ANY}));
icons_map[icon_url] = {CreateSquareIcon(sizes_px[i], colors[i])};
}
return icons_map;
}
WebAppProvider* provider() { return WebAppProvider::GetForTest(profile()); }
void LoadIconsFromDB(const webapps::AppId& app_id,
const std::vector<SquareSizePx>& sizes_px) {
BitmapData icon_bitmaps;
base::test::TestFuture<BitmapData> future;
WebAppIconManager& icon_manager = provider()->icon_manager();
// We can use this to test if icons of a specific size do not exist in the
// DB. This is to ensure we do not trigger the same condition as a DCHECK
// inside WebAppIconManager when calling ReadIcons().
if (!icon_manager.HasIcons(app_id, IconPurpose::ANY, sizes_px)) {
app_to_icons_data_[app_id] = icon_bitmaps;
return;
}
icon_manager.ReadIcons(app_id, IconPurpose::ANY, sizes_px,
future.GetCallback());
app_to_icons_data_[app_id] = future.Take();
}
std::vector<SquareSizePx> GetIconSizesForApp(const webapps::AppId& app_id) {
DCHECK(base::Contains(app_to_icons_data_, app_id));
return base::ToVector(
app_to_icons_data_[app_id],
[](const auto& icon_data) { return icon_data.first; });
}
std::vector<SkColor> GetIconColorsForApp(const webapps::AppId& app_id) {
DCHECK(base::Contains(app_to_icons_data_, app_id));
return base::ToVector(
app_to_icons_data_[app_id],
[](const auto& icon_data) { return icon_data.second.getColor(0, 0); });
}
void SetPageState(ExternalInstallOptions options,
const PageStateOptions& mock_options = {}) {
FakeWebContentsManager::FakePageState& state =
fake_web_contents_manager().GetOrCreatePageState(options.install_url);
state.manifest_before_default_processing = blink::mojom::Manifest::New();
state.manifest_before_default_processing->start_url = options.install_url;
if (mock_options.manifest_id.has_value()) {
state.manifest_before_default_processing->id = *mock_options.manifest_id;
} else {
state.manifest_before_default_processing->id =
GenerateManifestIdFromStartUrlOnly(options.install_url);
}
state.manifest_before_default_processing->name = u"Manifest Name";
state.return_null_info = mock_options.empty_web_app_info;
state.error_code = webapps::InstallableStatusCode::NO_ERROR_DETECTED;
state.url_load_result = mock_options.url_load_result;
}
bool IsPlaceholderAppUrl(const GURL& url) {
return registrar()
.LookupPlaceholderAppId(url, WebAppManagement::kPolicy)
.has_value();
}
bool IsPlaceholderAppId(const webapps::AppId& app_id) {
return registrar().IsPlaceholderApp(app_id, WebAppManagement::kPolicy);
}
WebAppRegistrar& registrar() { return fake_provider().registrar_unsafe(); }
FakeOsIntegrationManager* os_integration_manager() {
return WebAppProvider::GetForTest(profile())
->os_integration_manager()
.AsTestOsIntegrationManager();
}
TestShortcutManager* shortcut_manager() { return shortcut_manager_; }
FakeWebContentsManager& fake_web_contents_manager() {
return static_cast<FakeWebContentsManager&>(
fake_provider().web_contents_manager());
}
FakeWebAppUiManager& fake_ui_manager() {
return static_cast<FakeWebAppUiManager&>(fake_provider().ui_manager());
}
TestFileUtils& file_utils() {
return *fake_provider().file_utils()->AsTestFileUtils();
}
private:
base::flat_map<webapps::AppId, BitmapData> app_to_icons_data_;
raw_ptr<TestShortcutManager> shortcut_manager_ = nullptr;
raw_ptr<MockWebAppUiManager> ui_manager_ = nullptr;
};
TEST_F(ExternalAppResolutionCommandTest, SuccessInternalDefault) {
ExternalInstallOptions install_options(
kWebAppUrl, mojom::UserDisplayMode::kBrowser,
ExternalInstallSource::kInternalDefault);
SetPageState(install_options);
auto result = InstallAndWait(install_options);
EXPECT_EQ(result.code, webapps::InstallResultCode::kSuccessNewInstall);
ASSERT_TRUE(result.app_id.has_value());
EXPECT_TRUE(registrar().IsLocallyInstalled(*result.app_id));
EXPECT_FALSE(IsPlaceholderAppUrl(kWebAppUrl));
std::optional<webapps::AppId> id =
registrar().LookupExternalAppId(kWebAppUrl);
ASSERT_TRUE(id.has_value());
EXPECT_EQ(*result.app_id, *id);
EXPECT_EQ(0, fake_ui_manager().num_reparent_tab_calls());
EXPECT_TRUE(registrar().GetAppById(id.value()));
EXPECT_EQ(registrar().GetAppUserDisplayMode(id.value()),
mojom::UserDisplayMode::kBrowser);
EXPECT_EQ(registrar().GetLatestAppInstallSource(id.value()),
webapps::WebappInstallSource::INTERNAL_DEFAULT);
}
TEST_F(ExternalAppResolutionCommandTest, SuccessAppFromPolicy) {
ExternalInstallOptions install_options(
kWebAppUrl, mojom::UserDisplayMode::kBrowser,
ExternalInstallSource::kExternalDefault);
SetPageState(install_options);
auto result = InstallAndWait(install_options);
EXPECT_EQ(result.code, webapps::InstallResultCode::kSuccessNewInstall);
ASSERT_TRUE(result.app_id.has_value());
EXPECT_TRUE(registrar().IsLocallyInstalled(*result.app_id));
EXPECT_FALSE(IsPlaceholderAppUrl(kWebAppUrl));
std::optional<webapps::AppId> id =
registrar().LookupExternalAppId(kWebAppUrl);
ASSERT_TRUE(id.has_value());
EXPECT_EQ(*result.app_id, *id);
EXPECT_EQ(0, fake_ui_manager().num_reparent_tab_calls());
EXPECT_TRUE(registrar().GetAppById(id.value()));
EXPECT_EQ(registrar().GetAppUserDisplayMode(id.value()),
mojom::UserDisplayMode::kBrowser);
EXPECT_EQ(registrar().GetLatestAppInstallSource(id.value()),
webapps::WebappInstallSource::EXTERNAL_DEFAULT);
}
TEST_F(ExternalAppResolutionCommandTest, InstallFails) {
const GURL kWebAppUrl("https://foo.example");
ExternalInstallOptions install_options(
kWebAppUrl, mojom::UserDisplayMode::kBrowser,
ExternalInstallSource::kExternalDefault);
SetPageState(install_options, {.empty_web_app_info = true});
auto result = InstallAndWait(
install_options, fake_web_contents_manager().CreateDataRetriever());
std::optional<webapps::AppId> id =
registrar().LookupExternalAppId(kWebAppUrl);
EXPECT_EQ(webapps::InstallResultCode::kGetWebAppInstallInfoFailed,
result.code);
EXPECT_FALSE(result.app_id.has_value());
EXPECT_FALSE(id.has_value());
}
TEST_F(ExternalAppResolutionCommandTest, SuccessInstallPlaceholder) {
ExternalInstallOptions install_options(
kWebAppUrl, mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kExternalPolicy);
install_options.install_placeholder = true;
SetPageState(install_options,
{.url_load_result =
webapps::WebAppUrlLoaderResult::kRedirectedUrlLoaded});
auto result = InstallAndWait(install_options);
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, result.code);
EXPECT_TRUE(IsPlaceholderAppUrl(kWebAppUrl));
ASSERT_TRUE(result.app_id.has_value());
webapps::AppId app_id = result.app_id.value();
EXPECT_EQ(registrar().GetLatestAppInstallSource(app_id),
webapps::WebappInstallSource::EXTERNAL_POLICY);
EXPECT_EQ(registrar().GetAppShortName(app_id), kWebAppUrl.spec());
EXPECT_EQ(registrar().GetAppStartUrl(app_id), kWebAppUrl);
EXPECT_EQ(registrar().GetAppUserDisplayMode(app_id),
mojom::UserDisplayMode::kStandalone);
EXPECT_TRUE(registrar().GetAppIconInfos(app_id).empty());
EXPECT_TRUE(registrar().GetAppDownloadedIconSizesAny(app_id).empty());
EXPECT_FALSE(fake_provider().icon_manager().HasSmallestIcon(
app_id, {IconPurpose::ANY}, /*min_size=*/0));
}
TEST_F(ExternalAppResolutionCommandTest, InstallPlaceholderTwice) {
const GURL kWebAppUrl("https://foo.example");
ExternalInstallOptions options(kWebAppUrl,
mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kExternalPolicy);
options.install_placeholder = true;
webapps::AppId placeholder_app_id;
// Install a placeholder app.
{
SetPageState(options,
{.url_load_result =
webapps::WebAppUrlLoaderResult::kRedirectedUrlLoaded});
auto result = InstallAndWait(options);
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, result.code);
ASSERT_TRUE(result.app_id.has_value());
placeholder_app_id = result.app_id.value();
const WebApp* web_app = registrar().GetAppById(placeholder_app_id);
ASSERT_TRUE(web_app);
EXPECT_TRUE(web_app->HasOnlySource(WebAppManagement::Type::kPolicy));
EXPECT_TRUE(IsPlaceholderAppId(placeholder_app_id));
}
// Try to install it again.
SetPageState(options,
{.url_load_result =
webapps::WebAppUrlLoaderResult::kRedirectedUrlLoaded});
auto data_retriever = std::make_unique<FakeDataRetriever>();
data_retriever->BuildDefaultDataToRetrieve(kWebAppUrl, kWebAppScope);
auto result = InstallAndWait(options, std::move(data_retriever));
EXPECT_EQ(webapps::InstallResultCode::kSuccessAlreadyInstalled, result.code);
EXPECT_EQ(placeholder_app_id, result.app_id.value());
// It should still be a placeholder.
const WebApp* web_app = registrar().GetAppById(placeholder_app_id);
ASSERT_TRUE(web_app);
EXPECT_TRUE(web_app->HasOnlySource(WebAppManagement::Type::kPolicy));
EXPECT_TRUE(IsPlaceholderAppId(placeholder_app_id));
}
TEST_F(ExternalAppResolutionCommandTest, ReinstallPlaceholderSucceeds) {
const GURL kWebAppUrl("https://foo.example");
ExternalInstallOptions options(kWebAppUrl,
mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kExternalPolicy);
options.install_placeholder = true;
webapps::AppId placeholder_app_id;
// Install a placeholder app.
{
SetPageState(options,
{.url_load_result =
webapps::WebAppUrlLoaderResult::kRedirectedUrlLoaded});
auto result = InstallAndWait(options);
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, result.code);
ASSERT_TRUE(result.app_id.has_value());
placeholder_app_id = result.app_id.value();
const WebApp* web_app = registrar().GetAppById(placeholder_app_id);
ASSERT_TRUE(web_app);
EXPECT_TRUE(web_app->HasOnlySource(WebAppManagement::Type::kPolicy));
EXPECT_TRUE(IsPlaceholderAppId(placeholder_app_id));
}
// Replace the placeholder with a real app.
SetPageState(options);
auto data_retriever = std::make_unique<FakeDataRetriever>();
data_retriever->BuildDefaultDataToRetrieve(kWebAppUrl, kWebAppScope);
MockWebAppUiManager& ui_manager =
static_cast<MockWebAppUiManager&>(fake_ui_manager());
EXPECT_CALL(ui_manager, NotifyAppRelaunchState(_, _, _, _, _)).Times(0);
auto result = InstallAndWait(options, std::move(data_retriever));
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, result.code);
ASSERT_TRUE(result.app_id.has_value());
EXPECT_EQ(result.app_id.value(), placeholder_app_id);
const WebApp* web_app = registrar().GetAppById(placeholder_app_id);
ASSERT_TRUE(web_app);
EXPECT_TRUE(web_app->HasOnlySource(WebAppManagement::Type::kPolicy));
EXPECT_FALSE(IsPlaceholderAppUrl(kWebAppUrl));
EXPECT_FALSE(IsPlaceholderAppId(placeholder_app_id));
}
TEST_F(ExternalAppResolutionCommandTest,
ReinstallPlaceholderSucceedsWithAppRelaunch) {
const std::string origin = "https://foo.example";
const GURL kWebAppUrl(origin);
const GURL kManifestId(GURL(origin + "/id"));
ExternalInstallOptions options(kWebAppUrl,
mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kExternalPolicy);
options.install_placeholder = true;
webapps::AppId placeholder_app_id;
// Install a placeholder app.
{
SetPageState(options,
{.url_load_result =
webapps::WebAppUrlLoaderResult::kRedirectedUrlLoaded});
auto result = InstallAndWait(options);
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, result.code);
ASSERT_TRUE(result.app_id.has_value());
placeholder_app_id = result.app_id.value();
const WebApp* web_app = registrar().GetAppById(placeholder_app_id);
ASSERT_TRUE(web_app);
EXPECT_TRUE(web_app->HasOnlySource(WebAppManagement::Type::kPolicy));
EXPECT_TRUE(IsPlaceholderAppId(placeholder_app_id));
}
// Replace the placeholder with a real app.
const webapps::AppId final_app_id = GenerateAppIdFromManifestId(kManifestId);
options.placeholder_resolution_behavior =
PlaceholderResolutionBehavior::kCloseAndRelaunch;
SetPageState(options, {.manifest_id = kManifestId});
MockWebAppUiManager& ui_manager =
static_cast<MockWebAppUiManager&>(fake_ui_manager());
EXPECT_CALL(ui_manager, GetNumWindowsForApp(GenerateAppId(
/*manifest_id_path=*/std::nullopt, kWebAppUrl)))
.WillOnce(Return(1u));
EXPECT_CALL(ui_manager,
NotifyAppRelaunchState(placeholder_app_id, final_app_id, _, _,
AppRelaunchState::kAppAboutToRelaunch))
.Times(1);
EXPECT_CALL(ui_manager,
NotifyAppRelaunchState(placeholder_app_id, final_app_id, _, _,
AppRelaunchState::kAppClosingForRelaunch))
.Times(1);
EXPECT_CALL(ui_manager,
NotifyAppRelaunchState(placeholder_app_id, final_app_id, _, _,
AppRelaunchState::kAppRelaunched))
.Times(1);
auto result = InstallAndWait(options);
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, result.code);
ASSERT_TRUE(result.app_id.has_value());
EXPECT_EQ(result.app_id.value(), final_app_id);
const WebApp* web_app = registrar().GetAppById(placeholder_app_id);
ASSERT_FALSE(web_app);
const WebApp* final_web_app = registrar().GetAppById(final_app_id);
ASSERT_TRUE(final_web_app);
EXPECT_TRUE(final_web_app->HasOnlySource(WebAppManagement::Type::kPolicy));
EXPECT_FALSE(IsPlaceholderAppUrl(kWebAppUrl));
EXPECT_FALSE(IsPlaceholderAppId(placeholder_app_id));
EXPECT_FALSE(IsPlaceholderAppId(final_app_id));
}
TEST_F(ExternalAppResolutionCommandTest,
ReinstallPlaceholderAppRelaunchNoWindow) {
const std::string origin = "https://foo.example";
const GURL kWebAppUrl(origin);
const GURL kManifestId(GURL(origin + "/id"));
ExternalInstallOptions options(kWebAppUrl,
mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kExternalPolicy);
options.install_placeholder = true;
webapps::AppId placeholder_app_id;
// Install a placeholder app.
{
SetPageState(options,
{.url_load_result =
webapps::WebAppUrlLoaderResult::kRedirectedUrlLoaded});
auto result = InstallAndWait(options);
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, result.code);
ASSERT_TRUE(result.app_id.has_value());
placeholder_app_id = result.app_id.value();
const WebApp* web_app = registrar().GetAppById(placeholder_app_id);
ASSERT_TRUE(web_app);
EXPECT_TRUE(web_app->HasOnlySource(WebAppManagement::Type::kPolicy));
EXPECT_TRUE(IsPlaceholderAppId(placeholder_app_id));
}
// Replace the placeholder with a real app.
const webapps::AppId final_app_id = GenerateAppIdFromManifestId(kManifestId);
options.placeholder_resolution_behavior =
PlaceholderResolutionBehavior::kCloseAndRelaunch;
SetPageState(options, {.manifest_id = kManifestId});
MockWebAppUiManager& ui_manager =
static_cast<MockWebAppUiManager&>(fake_ui_manager());
EXPECT_CALL(ui_manager, GetNumWindowsForApp(GenerateAppId(
/*manifest_id_path=*/std::nullopt, kWebAppUrl)))
.WillOnce(Return(0u));
EXPECT_CALL(ui_manager, NotifyAppRelaunchState(_, _, _, _, _)).Times(0);
auto result = InstallAndWait(options);
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, result.code);
ASSERT_TRUE(result.app_id.has_value());
EXPECT_EQ(result.app_id.value(), final_app_id);
const WebApp* web_app = registrar().GetAppById(placeholder_app_id);
ASSERT_FALSE(web_app);
const WebApp* final_web_app = registrar().GetAppById(final_app_id);
ASSERT_TRUE(final_web_app);
EXPECT_TRUE(final_web_app->HasOnlySource(WebAppManagement::Type::kPolicy));
EXPECT_FALSE(IsPlaceholderAppUrl(kWebAppUrl));
EXPECT_FALSE(IsPlaceholderAppId(placeholder_app_id));
EXPECT_FALSE(IsPlaceholderAppId(final_app_id));
}
// With go/external_app_refactoring the placeholder is updated in-place and not
// uninstalled anymore.
TEST_F(ExternalAppResolutionCommandTest,
ReinstallPlaceholderMigrationSucceedsWithFailingFileDeletion) {
const GURL kWebAppUrl("https://foo.example");
ExternalInstallOptions options(kWebAppUrl,
mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kExternalPolicy);
options.install_placeholder = true;
webapps::AppId placeholder_app_id;
// Install a placeholder app.
{
webapps::AppId expected_app_id =
GenerateAppId(/*manifest_id_path=*/std::nullopt, kWebAppUrl);
SetPageState(options,
{.url_load_result =
webapps::WebAppUrlLoaderResult::kRedirectedUrlLoaded});
auto result = InstallAndWait(options);
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, result.code);
ASSERT_TRUE(result.app_id.has_value());
placeholder_app_id = result.app_id.value();
EXPECT_EQ(expected_app_id, placeholder_app_id);
EXPECT_TRUE(registrar()
.GetAppById(placeholder_app_id)
->HasOnlySource(WebAppManagement::Type::kPolicy));
EXPECT_TRUE(IsPlaceholderAppId(placeholder_app_id));
EXPECT_TRUE(registrar().IsInstalled(placeholder_app_id));
}
// Replace the placeholder with a real app.
SetPageState(options);
auto data_retriever = std::make_unique<FakeDataRetriever>();
data_retriever->BuildDefaultDataToRetrieve(kWebAppUrl, kWebAppScope);
// Simulate disk failure to uninstall the placeholder.
file_utils().SetNextDeleteFileRecursivelyResult(false);
auto result = InstallAndWait(options, std::move(data_retriever));
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, result.code);
ASSERT_TRUE(result.app_id.has_value());
EXPECT_EQ(result.app_id.value(), placeholder_app_id);
EXPECT_TRUE(registrar()
.GetAppById(placeholder_app_id)
->HasOnlySource(WebAppManagement::Type::kPolicy));
EXPECT_FALSE(IsPlaceholderAppUrl(kWebAppUrl));
EXPECT_FALSE(IsPlaceholderAppId(placeholder_app_id));
}
#if BUILDFLAG(IS_CHROMEOS)
TEST_F(ExternalAppResolutionCommandTest, InstallPlaceholderCustomName) {
const GURL kWebAppUrl("https://foo.example");
const std::string kCustomName("Custom äpp näme");
ExternalInstallOptions options(kWebAppUrl,
mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kExternalPolicy);
options.install_placeholder = true;
options.override_name = kCustomName;
SetPageState(options,
{.url_load_result =
webapps::WebAppUrlLoaderResult::kRedirectedUrlLoaded});
auto result = InstallAndWait(options);
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, result.code);
ASSERT_TRUE(result.app_id.has_value());
EXPECT_EQ(registrar().GetAppShortName(result.app_id.value()), kCustomName);
}
#endif // BUILDFLAG(IS_CHROMEOS)
TEST_F(ExternalAppResolutionCommandTest, UninstallAndReplace) {
const GURL kWebAppUrl("https://foo.example");
ExternalInstallOptions options = {kWebAppUrl, std::nullopt,
ExternalInstallSource::kInternalDefault};
webapps::AppId app_id;
{
// Migrate app1 and app2.
options.uninstall_and_replace = {"app1", "app2"};
SetPageState(options);
auto result = InstallAndWait(options);
ASSERT_TRUE(result.app_id.has_value());
app_id = result.app_id.value();
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, result.code);
EXPECT_EQ(result.app_id, *registrar().LookupExternalAppId(kWebAppUrl));
}
{
// Migration should run on every install of the app.
options.uninstall_and_replace = {"app3"};
SetPageState(options);
auto result = InstallAndWait(options);
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, result.code);
ASSERT_TRUE(result.app_id.has_value());
EXPECT_EQ(app_id, result.app_id.value());
}
}
TEST_F(ExternalAppResolutionCommandTest, InstallURLLoadFailed) {
const GURL kWebAppUrl("https://foo.example");
struct ResultPair {
webapps::WebAppUrlLoaderResult loader_result;
webapps::InstallResultCode install_result;
} result_pairs[] = {{webapps::WebAppUrlLoaderResult::kRedirectedUrlLoaded,
webapps::InstallResultCode::kInstallURLRedirected},
{webapps::WebAppUrlLoaderResult::kFailedUnknownReason,
webapps::InstallResultCode::kInstallURLLoadFailed},
{webapps::WebAppUrlLoaderResult::kFailedPageTookTooLong,
webapps::InstallResultCode::kInstallURLLoadTimeOut}};
for (const auto& result_pair : result_pairs) {
ExternalInstallOptions install_options(
kWebAppUrl, mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kInternalDefault);
fake_web_contents_manager()
.GetOrCreatePageState(install_options.install_url)
.url_load_result = result_pair.loader_result;
auto result = InstallAndWait(install_options);
EXPECT_EQ(result.code, result_pair.install_result);
}
}
TEST_F(ExternalAppResolutionCommandTest, InstallWithWebAppInfoSucceeds) {
base::HistogramTester tester;
const GURL kWebAppUrl("https://foo.example");
ExternalInstallOptions options(kWebAppUrl,
mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kExternalDefault);
options.only_use_app_info_factory = true;
options.app_info_factory = base::BindLambdaForTesting([&kWebAppUrl]() {
auto info = std::make_unique<WebAppInstallInfo>();
info->start_url = kWebAppUrl;
info->scope = kWebAppUrl.GetWithoutFilename();
info->title = u"Foo Web App";
return info;
});
auto result = InstallAndWait(options);
std::optional<webapps::AppId> id =
registrar().LookupExternalAppId(kWebAppUrl);
EXPECT_EQ(webapps::InstallResultCode::kSuccessOfflineOnlyInstall,
result.code);
ASSERT_TRUE(result.app_id.has_value());
webapps::AppId app_id = result.app_id.value();
EXPECT_FALSE(IsPlaceholderAppUrl(kWebAppUrl));
EXPECT_EQ(app_id, id.value_or("absent"));
EXPECT_EQ(fake_ui_manager().num_reparent_tab_calls(), 0);
EXPECT_EQ(registrar().GetAppUserDisplayMode(app_id),
mojom::UserDisplayMode::kStandalone);
EXPECT_EQ(registrar().GetLatestAppInstallSource(app_id),
webapps::WebappInstallSource::EXTERNAL_DEFAULT);
// Ensure that the WebApp.Install.Result histogram is only measured once.
tester.ExpectBucketCount("WebApp.Install.Result", /*sample=*/true,
/*expected_count=*/1);
}
TEST_F(ExternalAppResolutionCommandTest, InstallWithWebAppInfoFails) {
base::HistogramTester tester;
const GURL kWebAppUrl("https://foo.example");
ExternalInstallOptions options(kWebAppUrl,
mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kExternalDefault);
options.only_use_app_info_factory = true;
options.app_info_factory = base::BindLambdaForTesting([&kWebAppUrl]() {
auto info = std::make_unique<WebAppInstallInfo>();
info->start_url = kWebAppUrl;
info->scope = kWebAppUrl.GetWithoutFilename();
info->title = u"Foo Web App";
return info;
});
// Induce an error: Simulate "Disk Full" for writing icon files.
file_utils().SetRemainingDiskSpaceSize(0);
auto result = InstallAndWait(options);
std::optional<webapps::AppId> id =
registrar().LookupExternalAppId(kWebAppUrl);
EXPECT_EQ(webapps::InstallResultCode::kWriteDataFailed, result.code);
EXPECT_FALSE(result.app_id.has_value());
EXPECT_FALSE(id.has_value());
tester.ExpectBucketCount("WebApp.Install.Result", /*sample=*/false,
/*expected_count=*/1);
}
TEST_F(ExternalAppResolutionCommandTest, SucessInstallForcedContainerWindow) {
ExternalInstallOptions install_options(
kWebAppUrl, mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kInternalDefault);
SetPageState(install_options);
auto result = InstallAndWait(install_options);
EXPECT_EQ(result.code, webapps::InstallResultCode::kSuccessNewInstall);
ASSERT_TRUE(result.app_id.has_value());
EXPECT_TRUE(registrar().IsLocallyInstalled(*result.app_id));
EXPECT_FALSE(IsPlaceholderAppUrl(kWebAppUrl));
std::optional<webapps::AppId> id =
registrar().LookupExternalAppId(kWebAppUrl);
ASSERT_TRUE(id.has_value());
EXPECT_EQ(*result.app_id, *id);
EXPECT_EQ(0, fake_ui_manager().num_reparent_tab_calls());
EXPECT_TRUE(registrar().GetAppById(id.value()));
EXPECT_EQ(registrar().GetAppUserDisplayMode(id.value()),
mojom::UserDisplayMode::kStandalone);
EXPECT_EQ(registrar().GetLatestAppInstallSource(id.value()),
webapps::WebappInstallSource::INTERNAL_DEFAULT);
}
TEST_F(ExternalAppResolutionCommandTest, GetWebAppInstallInfoFailed) {
ExternalInstallOptions install_options(
kWebAppUrl, mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kExternalDefault);
SetPageState(
{kWebAppUrl, std::nullopt, ExternalInstallSource::kInternalDefault},
{.empty_web_app_info = true});
auto result = InstallAndWait(install_options);
EXPECT_EQ(result.code,
webapps::InstallResultCode::kGetWebAppInstallInfoFailed);
ASSERT_FALSE(result.app_id.has_value());
EXPECT_FALSE(registrar().IsLocallyInstalled(kWebAppId));
}
TEST_F(ExternalAppResolutionCommandTest,
InstallWebAppWithParams_DisplayModeFromWebAppInstallInfo) {
{
GURL url("https://example1.com/");
auto data_retriever = std::make_unique<FakeDataRetriever>();
data_retriever->BuildDefaultDataToRetrieve(url, url);
auto web_app_info = std::make_unique<WebAppInstallInfo>();
web_app_info->user_display_mode = mojom::UserDisplayMode::kBrowser;
data_retriever->SetRendererWebAppInstallInfo(std::move(web_app_info));
SetPageState({url, std::nullopt, ExternalInstallSource::kInternalDefault});
ExternalInstallOptions install_options(
url, /*user_display_mode=*/std::nullopt,
ExternalInstallSource::kExternalDefault);
auto result = InstallAndWait(install_options, std::move(data_retriever));
ASSERT_TRUE(result.app_id.has_value());
EXPECT_EQ(mojom::UserDisplayMode::kBrowser, provider()
->registrar_unsafe()
.GetAppById(*result.app_id)
->user_display_mode());
}
{
GURL url("https://example2.com/");
auto data_retriever = std::make_unique<FakeDataRetriever>();
data_retriever->BuildDefaultDataToRetrieve(url, url);
auto web_app_info = std::make_unique<WebAppInstallInfo>();
web_app_info->user_display_mode = mojom::UserDisplayMode::kStandalone;
data_retriever->SetRendererWebAppInstallInfo(std::move(web_app_info));
SetPageState({url, std::nullopt, ExternalInstallSource::kInternalDefault});
ExternalInstallOptions install_options(
url, /*user_display_mode=*/std::nullopt,
ExternalInstallSource::kExternalDefault);
auto result = InstallAndWait(install_options, std::move(data_retriever));
ASSERT_TRUE(result.app_id.has_value());
EXPECT_EQ(mojom::UserDisplayMode::kStandalone,
provider()
->registrar_unsafe()
.GetAppById(*result.app_id)
->user_display_mode());
}
}
TEST_F(ExternalAppResolutionCommandTest,
InstallWebAppWithParams_DisplayModeOverrideByExternalInstallOptions) {
{
GURL url("https://example3.com/");
auto data_retriever = std::make_unique<FakeDataRetriever>();
data_retriever->BuildDefaultDataToRetrieve(url, url);
auto web_app_info = std::make_unique<WebAppInstallInfo>();
web_app_info->user_display_mode = mojom::UserDisplayMode::kStandalone;
data_retriever->SetRendererWebAppInstallInfo(std::move(web_app_info));
SetPageState({url, std::nullopt, ExternalInstallSource::kInternalDefault});
ExternalInstallOptions install_options(
url, mojom::UserDisplayMode::kBrowser,
ExternalInstallSource::kExternalDefault);
auto result = InstallAndWait(install_options, std::move(data_retriever));
ASSERT_TRUE(result.app_id.has_value());
EXPECT_EQ(mojom::UserDisplayMode::kBrowser, provider()
->registrar_unsafe()
.GetAppById(*result.app_id)
->user_display_mode());
}
{
GURL url("https://example4.com/");
auto data_retriever = std::make_unique<FakeDataRetriever>();
data_retriever->BuildDefaultDataToRetrieve(url, url);
SetPageState({url, std::nullopt, ExternalInstallSource::kInternalDefault});
auto web_app_info = std::make_unique<WebAppInstallInfo>();
web_app_info->user_display_mode = mojom::UserDisplayMode::kBrowser;
data_retriever->SetRendererWebAppInstallInfo(std::move(web_app_info));
ExternalInstallOptions install_options(
url, mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kExternalDefault);
auto result = InstallAndWait(install_options, std::move(data_retriever));
ASSERT_TRUE(result.app_id.has_value());
EXPECT_EQ(mojom::UserDisplayMode::kStandalone,
provider()
->registrar_unsafe()
.GetAppById(*result.app_id)
->user_display_mode());
}
}
TEST_F(ExternalAppResolutionCommandTest, UpgradeLock) {
ExternalInstallOptions install_options(
kWebAppUrl, mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kExternalDefault);
auto data_retriever = std::make_unique<FakeDataRetriever>();
data_retriever->BuildDefaultDataToRetrieve(kWebAppUrl, kWebAppScope);
SetPageState(
{kWebAppUrl, std::nullopt, ExternalInstallSource::kInternalDefault});
base::flat_set<webapps::AppId> app_ids{
GenerateAppId(/*manifest_id_path=*/std::nullopt, kWebAppUrl)};
bool callback_command_run = false;
auto callback_command = std::make_unique<internal::CallbackCommand<AppLock>>(
"", AppLockDescription(app_ids),
base::BindLambdaForTesting(
[&](AppLock&, base::Value::Dict&) { callback_command_run = true; }),
/*completion_callback=*/base::DoNothing());
bool callback_command_2_run = false;
base::RunLoop callback_runloop;
auto callback_command_2 =
std::make_unique<internal::CallbackCommand<AppLock>>(
"", AppLockDescription(app_ids),
base::BindLambdaForTesting([&](AppLock&, base::Value::Dict&) {
callback_command_2_run = true;
}),
/*completion_callback=*/callback_runloop.QuitClosure());
base::RunLoop run_loop;
ExternallyManagedAppManager::InstallResult result;
webapps::AppId placeholder_app_id =
GenerateAppId(std::nullopt, install_options.install_url);
const bool is_placeholder_installed = registrar().IsPlaceholderApp(
placeholder_app_id,
ConvertExternalInstallSourceToSource(install_options.install_source));
auto command = std::make_unique<ExternalAppResolutionCommand>(
*profile(), install_options,
is_placeholder_installed
? std::optional<webapps::AppId>(placeholder_app_id)
: std::nullopt,
base::BindLambdaForTesting(
[&](ExternallyManagedAppManager::InstallResult install_result) {
result = std::move(install_result);
run_loop.Quit();
}));
// Schedules another callback command that acquires the same app lock after
// current command upgrades to app lock.
command->SetOnLockUpgradedCallbackForTesting(
base::BindLambdaForTesting([&]() {
provider()->command_manager().ScheduleCommand(
std::move(callback_command_2));
}));
provider()->command_manager().ScheduleCommand(std::move(command));
// Immediately schedule a callback command, this will request the app lock
// before the ExternallyManagedInstallCommand.
provider()->command_manager().ScheduleCommand(std::move(callback_command));
run_loop.Run();
EXPECT_EQ(result.code, webapps::InstallResultCode::kSuccessNewInstall);
ASSERT_TRUE(result.app_id.has_value());
EXPECT_TRUE(registrar().IsLocallyInstalled(*result.app_id));
EXPECT_TRUE(callback_command_run);
EXPECT_FALSE(callback_command_2_run);
callback_runloop.Run();
EXPECT_TRUE(callback_command_2_run);
}
TEST_F(ExternalAppResolutionCommandTest,
IconDownloadSuccessOverwriteNoIconsBefore) {
// First install the app normally.
{
auto data_retriever = std::make_unique<FakeDataRetriever>();
data_retriever->BuildDefaultDataToRetrieve(kWebAppUrl, kWebAppScope);
auto web_app_info = std::make_unique<WebAppInstallInfo>();
web_app_info->user_display_mode = mojom::UserDisplayMode::kStandalone;
data_retriever->SetRendererWebAppInstallInfo(std::move(web_app_info));
SetPageState(
{kWebAppUrl, std::nullopt, ExternalInstallSource::kInternalDefault});
ExternalInstallOptions install_options(
kWebAppUrl, mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kExternalDefault);
auto result = InstallAndWait(install_options, std::move(data_retriever));
EXPECT_EQ(result.code, webapps::InstallResultCode::kSuccessNewInstall);
ASSERT_TRUE(result.app_id.has_value());
EXPECT_EQ(0u, registrar().GetAppIconInfos(*result.app_id).size());
}
// Now install the same app with a manifest containing valid icons and
// verify successful icon downloads and writes to DB.
{
std::vector<SquareSizePx> new_sizes{icon_size::k64, icon_size::k512};
std::vector<SkColor> icon_colors{SK_ColorGREEN, SK_ColorRED};
auto manifest = CreateValidManifest();
IconsMap icons_map =
CreateAndLoadIconMapFromSizes(new_sizes, icon_colors, manifest.get());
DownloadedIconsHttpResults http_results;
for (const auto& url_and_bitmap : icons_map) {
http_results[IconUrlWithSize::CreateForUnspecifiedSize(
url_and_bitmap.first)] = net::HttpStatusCode::HTTP_OK;
}
// Set up data retriever and load everything.
auto new_data_retriever = std::make_unique<FakeDataRetriever>();
new_data_retriever->SetIconsDownloadedResult(
IconsDownloadedResult::kCompleted);
new_data_retriever->SetDownloadedIconsHttpResults(std::move(http_results));
new_data_retriever->SetIcons(std::move(icons_map));
new_data_retriever->SetManifest(
std::move(manifest), webapps::InstallableStatusCode::NO_ERROR_DETECTED);
new_data_retriever->SetEmptyRendererWebAppInstallInfo();
SetPageState(
{kWebAppUrl, std::nullopt, ExternalInstallSource::kInternalDefault});
ExternalInstallOptions new_install_options(
kWebAppUrl, mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kExternalPolicy);
auto result =
InstallAndWait(new_install_options, std::move(new_data_retriever));
ASSERT_TRUE(result.app_id.has_value());
const webapps::AppId& installed_app_id = *result.app_id;
EXPECT_EQ(result.code, webapps::InstallResultCode::kSuccessNewInstall);
// Verify icon information.
const std::vector<apps::IconInfo> icon_info =
registrar().GetAppIconInfos(installed_app_id);
EXPECT_EQ(new_sizes.size(), icon_info.size());
EXPECT_EQ(GetMockIconInfo(icon_size::k64), icon_info[0]);
EXPECT_EQ(GetMockIconInfo(icon_size::k512), icon_info[1]);
LoadIconsFromDB(installed_app_id, new_sizes);
EXPECT_EQ(GetIconSizesForApp(installed_app_id), new_sizes);
EXPECT_EQ(GetIconColorsForApp(installed_app_id), icon_colors);
}
}
TEST_F(ExternalAppResolutionCommandTest, IconDownloadSuccessOverwriteOldIcons) {
// First install the app normally, having 1 icon only.
{
std::vector<SquareSizePx> old_sizes{icon_size::k256};
std::vector<SkColor> old_colors{SK_ColorBLUE};
auto manifest = CreateValidManifest();
IconsMap icons_map =
CreateAndLoadIconMapFromSizes(old_sizes, old_colors, manifest.get());
DownloadedIconsHttpResults http_results;
for (const auto& url_and_bitmap : icons_map) {
http_results[IconUrlWithSize::CreateForUnspecifiedSize(
url_and_bitmap.first)] = net::HttpStatusCode::HTTP_OK;
}
auto web_app_info = std::make_unique<WebAppInstallInfo>();
web_app_info->user_display_mode = mojom::UserDisplayMode::kStandalone;
auto data_retriever = std::make_unique<FakeDataRetriever>();
data_retriever->BuildDefaultDataToRetrieve(kWebAppUrl, kWebAppScope);
data_retriever->SetIconsDownloadedResult(IconsDownloadedResult::kCompleted);
data_retriever->SetDownloadedIconsHttpResults(std::move(http_results));
data_retriever->SetIcons(std::move(icons_map));
data_retriever->SetManifest(
std::move(manifest), webapps::InstallableStatusCode::NO_ERROR_DETECTED);
data_retriever->SetRendererWebAppInstallInfo(std::move(web_app_info));
SetPageState(
{kWebAppUrl, std::nullopt, ExternalInstallSource::kInternalDefault});
ExternalInstallOptions install_options(
kWebAppUrl, mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kExternalDefault);
auto result = InstallAndWait(install_options, std::move(data_retriever));
ASSERT_TRUE(result.app_id.has_value());
const webapps::AppId& installed_app_id = *result.app_id;
EXPECT_EQ(result.code, webapps::InstallResultCode::kSuccessNewInstall);
const std::vector<apps::IconInfo>& icons_info_pre_reinstall =
registrar().GetAppIconInfos(installed_app_id);
EXPECT_EQ(old_sizes.size(), icons_info_pre_reinstall.size());
EXPECT_EQ(GetMockIconInfo(icon_size::k256), icons_info_pre_reinstall[0]);
LoadIconsFromDB(installed_app_id, old_sizes);
EXPECT_EQ(GetIconSizesForApp(installed_app_id), old_sizes);
EXPECT_EQ(GetIconColorsForApp(installed_app_id), old_colors);
}
// Now install the same app with a manifest containing different icons from
// before, and verify that the icons are updated in the DB.
{
std::vector<SquareSizePx> new_sizes{icon_size::k64, icon_size::k512};
std::vector<SkColor> new_colors{SK_ColorYELLOW, SK_ColorRED};
auto new_manifest = CreateValidManifest();
IconsMap new_icons_map = CreateAndLoadIconMapFromSizes(
new_sizes, new_colors, new_manifest.get());
DownloadedIconsHttpResults new_http_results;
for (const auto& url_and_bitmap : new_icons_map) {
new_http_results[IconUrlWithSize::CreateForUnspecifiedSize(
url_and_bitmap.first)] = net::HttpStatusCode::HTTP_OK;
}
// Set up data retriever and load everything.
auto new_data_retriever = std::make_unique<FakeDataRetriever>();
new_data_retriever->SetIconsDownloadedResult(
IconsDownloadedResult::kCompleted);
new_data_retriever->SetDownloadedIconsHttpResults(
std::move(new_http_results));
new_data_retriever->SetIcons(std::move(new_icons_map));
new_data_retriever->SetManifest(
std::move(new_manifest),
webapps::InstallableStatusCode::NO_ERROR_DETECTED);
new_data_retriever->SetEmptyRendererWebAppInstallInfo();
SetPageState(
{kWebAppUrl, std::nullopt, ExternalInstallSource::kInternalDefault});
ExternalInstallOptions new_install_options(
kWebAppUrl, mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kExternalPolicy);
auto updated_result =
InstallAndWait(new_install_options, std::move(new_data_retriever));
ASSERT_TRUE(updated_result.app_id.has_value());
const webapps::AppId& updated_app_id = *updated_result.app_id;
EXPECT_EQ(updated_result.code,
webapps::InstallResultCode::kSuccessNewInstall);
// Verify icon information.
const std::vector<apps::IconInfo> new_icon_info =
registrar().GetAppIconInfos(updated_app_id);
EXPECT_EQ(new_sizes.size(), new_icon_info.size());
EXPECT_EQ(GetMockIconInfo(icon_size::k64), new_icon_info[0]);
EXPECT_EQ(GetMockIconInfo(icon_size::k512), new_icon_info[1]);
LoadIconsFromDB(updated_app_id, new_sizes);
EXPECT_EQ(GetIconSizesForApp(updated_app_id), new_sizes);
EXPECT_EQ(GetIconColorsForApp(updated_app_id), new_colors);
}
}
TEST_F(ExternalAppResolutionCommandTest,
IconDownloadFailDoesNotOverwriteOldIcons) {
// First install the app normally, having 1 icon only.
std::vector<SquareSizePx> old_sizes{icon_size::k64};
std::vector<SkColor> old_colors{SK_ColorRED};
{
auto manifest = CreateValidManifest();
IconsMap icons_map =
CreateAndLoadIconMapFromSizes(old_sizes, old_colors, manifest.get());
DownloadedIconsHttpResults http_results;
for (const auto& url_and_bitmap : icons_map) {
http_results[IconUrlWithSize::CreateForUnspecifiedSize(
url_and_bitmap.first)] = net::HttpStatusCode::HTTP_OK;
}
auto web_app_info = std::make_unique<WebAppInstallInfo>();
web_app_info->user_display_mode = mojom::UserDisplayMode::kStandalone;
auto data_retriever = std::make_unique<FakeDataRetriever>();
data_retriever->BuildDefaultDataToRetrieve(kWebAppUrl, kWebAppScope);
data_retriever->SetIconsDownloadedResult(IconsDownloadedResult::kCompleted);
data_retriever->SetDownloadedIconsHttpResults(std::move(http_results));
data_retriever->SetIcons(std::move(icons_map));
data_retriever->SetManifest(
std::move(manifest), webapps::InstallableStatusCode::NO_ERROR_DETECTED);
data_retriever->SetRendererWebAppInstallInfo(std::move(web_app_info));
SetPageState(
{kWebAppUrl, std::nullopt, ExternalInstallSource::kInternalDefault});
ExternalInstallOptions install_options(
kWebAppUrl, mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kExternalDefault);
auto result = InstallAndWait(install_options, std::move(data_retriever));
ASSERT_TRUE(result.app_id.has_value());
const webapps::AppId& installed_app_id = *result.app_id;
EXPECT_EQ(result.code, webapps::InstallResultCode::kSuccessNewInstall);
const std::vector<apps::IconInfo>& icons_info_pre_reinstall =
registrar().GetAppIconInfos(installed_app_id);
EXPECT_EQ(1u, icons_info_pre_reinstall.size());
EXPECT_EQ(GetMockIconInfo(icon_size::k64), icons_info_pre_reinstall[0]);
LoadIconsFromDB(installed_app_id, old_sizes);
EXPECT_EQ(GetIconSizesForApp(installed_app_id), old_sizes);
EXPECT_EQ(GetIconColorsForApp(installed_app_id), old_colors);
}
// Now install the same app with a manifest containing multiple icons, and
// fail the icon downloading. This should prevent icons from getting
// overwritten in the DB.
{
std::vector<SquareSizePx> new_sizes{icon_size::k256, icon_size::k512};
std::vector<SkColor> new_colors{SK_ColorGREEN, SK_ColorBLUE};
auto new_manifest = CreateValidManifest();
IconsMap new_icons_map = CreateAndLoadIconMapFromSizes(
new_sizes, new_colors, new_manifest.get());
DownloadedIconsHttpResults new_http_results;
for (const auto& url_and_bitmap : new_icons_map) {
new_http_results[IconUrlWithSize::CreateForUnspecifiedSize(
url_and_bitmap.first)] = net::HttpStatusCode::HTTP_OK;
}
// Set up data retriever and load everything.
auto new_data_retriever = std::make_unique<FakeDataRetriever>();
new_data_retriever->SetIconsDownloadedResult(
IconsDownloadedResult::kAbortedDueToFailure);
new_data_retriever->SetDownloadedIconsHttpResults(
std::move(new_http_results));
new_data_retriever->SetIcons(std::move(new_icons_map));
new_data_retriever->SetManifest(
std::move(new_manifest),
webapps::InstallableStatusCode::NO_ERROR_DETECTED);
new_data_retriever->SetEmptyRendererWebAppInstallInfo();
SetPageState(
{kWebAppUrl, std::nullopt, ExternalInstallSource::kInternalDefault});
ExternalInstallOptions new_install_options(
kWebAppUrl, mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kExternalPolicy);
auto updated_result =
InstallAndWait(new_install_options, std::move(new_data_retriever));
ASSERT_TRUE(updated_result.app_id.has_value());
const webapps::AppId& installed_app_id = *updated_result.app_id;
EXPECT_EQ(updated_result.code,
webapps::InstallResultCode::kSuccessNewInstall);
// Verify icon information, that new data is not written into the DB
// and the old data still persists.
const std::vector<apps::IconInfo> new_icon_info =
registrar().GetAppIconInfos(*updated_result.app_id);
EXPECT_NE(new_sizes.size(), new_icon_info.size());
LoadIconsFromDB(installed_app_id, new_sizes);
// Trying to load new icons returns blank data because the new sizes and
// colors have not been overwritten.
EXPECT_EQ(0u, GetIconSizesForApp(installed_app_id).size());
EXPECT_EQ(0u, GetIconColorsForApp(installed_app_id).size());
EXPECT_EQ(old_sizes.size(), new_icon_info.size());
EXPECT_EQ(GetMockIconInfo(icon_size::k64), new_icon_info[0]);
LoadIconsFromDB(installed_app_id, old_sizes);
EXPECT_EQ(GetIconSizesForApp(installed_app_id), old_sizes);
EXPECT_EQ(GetIconColorsForApp(installed_app_id), old_colors);
}
}
#if !BUILDFLAG(IS_CHROMEOS_LACROS)
TEST_F(ExternalAppResolutionCommandTest, SuccessWithUninstallAndReplace) {
GURL old_app_url("http://old-app.com");
const webapps::AppId old_app =
test::InstallDummyWebApp(profile(), "old_app", old_app_url);
auto shortcut_info = std::make_unique<ShortcutInfo>();
shortcut_info->url = old_app_url;
shortcut_manager()->SetShortcutInfoForApp(old_app, std::move(shortcut_info));
ShortcutLocations shortcut_locations;
shortcut_locations.on_desktop = false;
shortcut_locations.in_quick_launch_bar = true;
shortcut_locations.in_startup = true;
shortcut_manager()->SetAppExistingShortcuts(old_app_url, shortcut_locations);
ExternalInstallOptions install_options(
kWebAppUrl, mojom::UserDisplayMode::kStandalone,
ExternalInstallSource::kExternalDefault);
install_options.uninstall_and_replace = {old_app};
auto data_retriever = std::make_unique<FakeDataRetriever>();
data_retriever->BuildDefaultDataToRetrieve(kWebAppUrl, kWebAppScope);
SetPageState(
{kWebAppUrl, std::nullopt, ExternalInstallSource::kInternalDefault});
auto result = InstallAndWait(install_options, std::move(data_retriever));
EXPECT_EQ(result.code, webapps::InstallResultCode::kSuccessNewInstall);
ASSERT_TRUE(result.app_id.has_value());
EXPECT_TRUE(registrar().IsLocallyInstalled(*result.app_id));
std::optional<proto::WebAppOsIntegrationState> os_state =
registrar().GetAppCurrentOsIntegrationState(*result.app_id);
ASSERT_TRUE(os_state.has_value());
EXPECT_TRUE(os_state->has_shortcut());
EXPECT_EQ(os_state->run_on_os_login().run_on_os_login_mode(),
proto::RunOnOsLoginMode::WINDOWED);
}
#endif // !BUILDFLAG(IS_CHROMEOS_LACROS)
TEST_F(ExternalAppResolutionCommandTest, WriteDataToDiskFailed) {
ExternalInstallOptions install_options(
kWebAppUrl, mojom::UserDisplayMode::kBrowser,
ExternalInstallSource::kExternalDefault);
SetPageState(install_options);
// Induce an error: Simulate "Disk Full" for writing icon files.
file_utils().SetRemainingDiskSpaceSize(1024);
// Expect app installation to fail and not lead to a crash.
auto result = InstallAndWait(install_options);
EXPECT_EQ(result.code, webapps::InstallResultCode::kWriteDataFailed);
ASSERT_FALSE(result.app_id.has_value());
}
} // namespace
} // namespace web_app