blob: 68660771cf8e8b6900fd6f0ffc7161a50d0ff29f [file] [log] [blame]
// Copyright 2019 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/web_applications/web_app_install_manager.h"
#include <memory>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/containers/flat_set.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/web_applications/externally_installed_web_app_prefs.h"
#include "chrome/browser/web_applications/policy/web_app_policy_manager.h"
#include "chrome/browser/web_applications/test/fake_data_retriever.h"
#include "chrome/browser/web_applications/test/fake_web_app_database_factory.h"
#include "chrome/browser/web_applications/test/fake_web_app_registry_controller.h"
#include "chrome/browser/web_applications/test/fake_web_app_ui_manager.h"
#include "chrome/browser/web_applications/test/test_file_utils.h"
#include "chrome/browser/web_applications/test/test_web_app_url_loader.h"
#include "chrome/browser/web_applications/test/web_app_icon_test_utils.h"
#include "chrome/browser/web_applications/test/web_app_sync_test_utils.h"
#include "chrome/browser/web_applications/test/web_app_test.h"
#include "chrome/browser/web_applications/test/web_app_test_observers.h"
#include "chrome/browser/web_applications/test/web_app_test_utils.h"
#include "chrome/browser/web_applications/user_display_mode.h"
#include "chrome/browser/web_applications/web_app.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_finalizer.h"
#include "chrome/browser/web_applications/web_app_install_info.h"
#include "chrome/browser/web_applications/web_app_install_task.h"
#include "chrome/browser/web_applications/web_app_install_utils.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/browser/web_applications/web_app_sync_bridge.h"
#include "chrome/browser/web_applications/web_app_uninstall_job.h"
#include "chrome/browser/web_applications/web_app_utils.h"
#include "chrome/test/base/testing_profile.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 "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/manifest/display_mode.mojom-shared.h"
#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
#include "third_party/skia/include/core/SkColor.h"
#include "url/gurl.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ash/constants/ash_features.h"
#include "chrome/common/chrome_features.h"
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
namespace web_app {
namespace {
constexpr SquareSizePx kDefaultImageSize = 100;
constexpr char kIconUrl[] = "https://example.com/app.ico";
std::unique_ptr<WebAppInstallInfo> ConvertWebAppToRendererWebAppInstallInfo(
const WebApp& app) {
auto install_info = std::make_unique<WebAppInstallInfo>();
// Most fields are expected to be populated by a manifest data in a subsequent
// override install process data flow. TODO(loyso): Make it more robust.
install_info->description = base::UTF8ToUTF16(app.untranslated_description());
// |user_display_mode| is a user's display mode value and it is typically
// populated by a UI dialog in production code. We set it here for testing
// purposes.
CHECK(app.user_display_mode().has_value());
install_info->user_display_mode = *app.user_display_mode();
return install_info;
}
std::vector<blink::Manifest::ImageResource> ConvertWebAppIconsToImageResources(
const WebApp& app) {
std::vector<blink::Manifest::ImageResource> icons;
for (const apps::IconInfo& icon_info : app.manifest_icons()) {
blink::Manifest::ImageResource icon;
icon.src = icon_info.url;
// TODO(estade): remove this cast.
icon.purpose.push_back(static_cast<IconPurpose>(icon_info.purpose));
icon.sizes.emplace_back(
icon_info.square_size_px.value_or(kDefaultImageSize),
icon_info.square_size_px.value_or(kDefaultImageSize));
icons.push_back(std::move(icon));
}
return icons;
}
blink::mojom::ManifestPtr ConvertWebAppToManifest(const WebApp& app) {
auto manifest = blink::mojom::Manifest::New();
manifest->start_url = app.start_url();
manifest->scope = app.start_url();
manifest->short_name = u"Short Name to be overriden.";
manifest->name = base::UTF8ToUTF16(app.untranslated_name());
if (app.theme_color()) {
manifest->has_theme_color = true;
manifest->theme_color = *app.theme_color();
}
manifest->display = app.display_mode();
manifest->icons = ConvertWebAppIconsToImageResources(app);
return manifest;
}
IconsMap ConvertWebAppIconsToIconsMap(const WebApp& app) {
IconsMap icons_map;
for (const apps::IconInfo& icon_info : app.manifest_icons()) {
icons_map[icon_info.url] = {CreateSquareIcon(
icon_info.square_size_px.value_or(kDefaultImageSize), SK_ColorBLACK)};
}
return icons_map;
}
std::unique_ptr<WebAppDataRetriever> ConvertWebAppToDataRetriever(
const WebApp& app) {
auto data_retriever = std::make_unique<FakeDataRetriever>();
data_retriever->SetRendererWebAppInstallInfo(
ConvertWebAppToRendererWebAppInstallInfo(app));
data_retriever->SetManifest(ConvertWebAppToManifest(app),
/*is_installable=*/true);
data_retriever->SetIcons(ConvertWebAppIconsToIconsMap(app));
return std::unique_ptr<WebAppDataRetriever>(std::move(data_retriever));
}
std::unique_ptr<WebAppDataRetriever> CreateEmptyDataRetriever() {
auto data_retriever = std::make_unique<FakeDataRetriever>();
return std::unique_ptr<WebAppDataRetriever>(std::move(data_retriever));
}
std::unique_ptr<WebAppInstallTask> CreateDummyTask() {
return std::make_unique<WebAppInstallTask>(
/*profile=*/nullptr,
/*install_finalizer=*/nullptr,
/*data_retriever=*/nullptr,
/*registrar=*/nullptr, webapps::WebappInstallSource::SYNC);
}
// TODO(crbug.com/1194709): Retire SyncParam after Lacros ships.
enum class SyncParam { kWithoutSync = 0, kWithSync = 1, kMaxValue = kWithSync };
} // namespace
class WebAppInstallManagerTest
: public WebAppTest,
public ::testing::WithParamInterface<SyncParam> {
public:
WebAppInstallManagerTest() {
#if BUILDFLAG(IS_CHROMEOS_ASH)
if (GetParam() == SyncParam::kWithSync) {
// Disable WebAppsCrosapi, so that Web Apps get synced in the Ash browser.
scoped_feature_list_.InitWithFeatures(
{}, {features::kWebAppsCrosapi, chromeos::features::kLacrosPrimary});
} else {
// Enable WebAppsCrosapi, so that Web Apps don't get synced in the Ash
// browser.
scoped_feature_list_.InitAndEnableFeature(features::kWebAppsCrosapi);
}
#else
DCHECK(GetParam() == SyncParam::kWithSync);
#endif
}
void SetUp() override {
WebAppTest::SetUp();
externally_installed_app_prefs_ =
std::make_unique<ExternallyInstalledWebAppPrefs>(profile()->GetPrefs());
fake_registry_controller_ =
std::make_unique<FakeWebAppRegistryController>();
fake_registry_controller_->SetUp(profile());
file_utils_ = base::MakeRefCounted<TestFileUtils>();
icon_manager_ = std::make_unique<WebAppIconManager>(profile(), file_utils_);
policy_manager_ = std::make_unique<WebAppPolicyManager>(profile());
install_finalizer_ = std::make_unique<WebAppInstallFinalizer>(profile());
install_manager_ = std::make_unique<WebAppInstallManager>(profile());
install_manager_->SetSubsystems(&registrar(),
&controller().os_integration_manager(),
install_finalizer_.get());
auto test_url_loader = std::make_unique<TestWebAppUrlLoader>();
test_url_loader_ = test_url_loader.get();
install_manager_->SetUrlLoaderForTesting(std::move(test_url_loader));
ui_manager_ = std::make_unique<FakeWebAppUiManager>();
icon_manager_->SetSubsystems(&registrar(), &install_manager());
install_finalizer_->SetSubsystems(
&install_manager(), &registrar(), ui_manager_.get(),
&fake_registry_controller_->sync_bridge(),
&fake_registry_controller_->os_integration_manager(),
icon_manager_.get(), policy_manager_.get(),
&fake_registry_controller_->translation_manager());
}
void TearDown() override {
DestroyManagers();
WebAppTest::TearDown();
}
WebAppRegistrar& registrar() { return controller().registrar(); }
WebAppInstallManager& install_manager() { return *install_manager_; }
WebAppInstallFinalizer& finalizer() { return *install_finalizer_; }
WebAppIconManager& icon_manager() { return *icon_manager_; }
TestWebAppUrlLoader& url_loader() { return *test_url_loader_; }
TestFileUtils& file_utils() {
DCHECK(file_utils_);
return *file_utils_;
}
FakeWebAppRegistryController& controller() {
return *fake_registry_controller_;
}
ExternallyInstalledWebAppPrefs& externally_installed_app_prefs() {
return *externally_installed_app_prefs_;
}
std::unique_ptr<WebApp> CreateWebAppFromSyncAndPendingInstallation(
const GURL& start_url,
const std::string& app_name,
absl::optional<UserDisplayMode> user_display_mode,
SkColor theme_color,
bool is_locally_installed,
const GURL& scope,
const std::vector<apps::IconInfo>& icon_infos) {
auto web_app = test::CreateWebApp(start_url, WebAppManagement::kSync);
web_app->SetIsFromSyncAndPendingInstallation(true);
web_app->SetIsLocallyInstalled(is_locally_installed);
DCHECK(user_display_mode.has_value());
web_app->SetUserDisplayMode(*user_display_mode);
WebApp::SyncFallbackData sync_fallback_data;
sync_fallback_data.name = app_name;
sync_fallback_data.theme_color = theme_color;
sync_fallback_data.scope = scope;
sync_fallback_data.icon_infos = icon_infos;
web_app->SetSyncFallbackData(std::move(sync_fallback_data));
return web_app;
}
void InitEmptyRegistrar() {
controller().Init();
install_finalizer_->Start();
install_manager_->Start();
}
std::set<AppId> InitRegistrarWithRegistry(const Registry& registry) {
std::set<AppId> app_ids;
for (auto& kv : registry)
app_ids.insert(kv.second->app_id());
controller().database_factory().WriteRegistry(registry);
controller().Init();
install_finalizer_->Start();
install_manager_->Start();
return app_ids;
}
AppId InitRegistrarWithApp(std::unique_ptr<WebApp> app) {
DCHECK(registrar().is_empty());
AppId app_id = app->app_id();
Registry registry;
registry.emplace(app_id, std::move(app));
InitRegistrarWithRegistry(registry);
return app_id;
}
struct InstallResult {
AppId app_id;
webapps::InstallResultCode code;
};
InstallResult InstallWebAppFromManifestWithFallback() {
InstallResult result;
base::RunLoop run_loop;
install_manager().InstallWebAppFromManifestWithFallback(
web_contents(), WebAppInstallFlow::kInstallSite,
webapps::WebappInstallSource::OMNIBOX_INSTALL_ICON,
base::BindOnce(test::TestAcceptDialogCallback),
base::BindLambdaForTesting([&](const AppId& installed_app_id,
webapps::InstallResultCode code) {
result.app_id = installed_app_id;
result.code = code;
run_loop.Quit();
}));
run_loop.Run();
return result;
}
InstallResult InstallSubApp(const AppId& parent_app_id,
const GURL& install_url) {
UseDefaultDataRetriever(install_url);
url_loader().AddPrepareForLoadResults(
{WebAppUrlLoader::Result::kUrlLoaded});
url_loader().SetNextLoadUrlResult(install_url,
WebAppUrlLoader::Result::kUrlLoaded);
InstallResult result;
base::RunLoop run_loop;
install_manager().InstallSubApp(
parent_app_id, install_url,
base::BindLambdaForTesting([&](const AppId& installed_app_id,
webapps::InstallResultCode code) {
result.app_id = installed_app_id;
result.code = code;
run_loop.Quit();
}));
run_loop.Run();
return result;
}
InstallResult InstallWebAppsAfterSync(std::vector<WebApp*> web_apps) {
InstallResult result;
base::RunLoop run_loop;
install_manager().InstallWebAppsAfterSync(
std::move(web_apps),
base::BindLambdaForTesting(
[&](const AppId& app_id, webapps::InstallResultCode code) {
result.app_id = app_id;
result.code = code;
run_loop.Quit();
}));
run_loop.Run();
return result;
}
InstallResult InstallWebAppFromInfo(
std::unique_ptr<WebAppInstallInfo> install_info,
bool overwrite_existing_manifest_fields,
webapps::WebappInstallSource install_source) {
InstallResult result;
base::RunLoop run_loop;
install_manager().InstallWebAppFromInfo(
std::move(install_info), overwrite_existing_manifest_fields,
ForInstallableSite::kYes, install_source,
base::BindLambdaForTesting([&](const AppId& installed_app_id,
webapps::InstallResultCode code) {
result.app_id = installed_app_id;
result.code = code;
run_loop.Quit();
}));
run_loop.Run();
return result;
}
std::map<SquareSizePx, SkBitmap> ReadIcons(const AppId& app_id,
IconPurpose purpose,
const SortedSizesPx& sizes_px) {
std::map<SquareSizePx, SkBitmap> result;
base::RunLoop run_loop;
icon_manager().ReadIcons(
app_id, purpose, sizes_px,
base::BindLambdaForTesting(
[&](std::map<SquareSizePx, SkBitmap> icon_bitmaps) {
result = std::move(icon_bitmaps);
run_loop.Quit();
}));
run_loop.Run();
return result;
}
int GetNumFullyInstalledApps() const {
int num_apps = 0;
for ([[maybe_unused]] const WebApp& app :
fake_registry_controller_->registrar().GetApps()) {
++num_apps;
}
return num_apps;
}
webapps::UninstallResultCode UninstallPolicyWebAppByUrl(const GURL& app_url) {
webapps::UninstallResultCode result;
base::RunLoop run_loop;
finalizer().UninstallExternalWebAppByUrl(
app_url, WebAppManagement::kPolicy,
webapps::WebappUninstallSource::kExternalPolicy,
base::BindLambdaForTesting([&](webapps::UninstallResultCode code) {
result = code;
run_loop.Quit();
}));
run_loop.Run();
return result;
}
webapps::UninstallResultCode UninstallWebApp(const AppId& app_id) {
webapps::UninstallResultCode result;
base::RunLoop run_loop;
finalizer().UninstallWebApp(
app_id, webapps::WebappUninstallSource::kAppMenu,
base::BindLambdaForTesting([&](webapps::UninstallResultCode code) {
result = code;
run_loop.Quit();
}));
run_loop.Run();
return result;
}
void UseDefaultDataRetriever(const GURL& start_url) {
install_manager().SetDataRetrieverFactoryForTesting(
base::BindLambdaForTesting([start_url]() {
auto data_retriever = std::make_unique<FakeDataRetriever>();
data_retriever->BuildDefaultDataToRetrieve(start_url, start_url);
return std::unique_ptr<WebAppDataRetriever>(
std::move(data_retriever));
}));
}
void DestroyManagers() {
// The reverse order of creation:
ui_manager_.reset();
policy_manager_.reset();
icon_manager_.reset();
fake_registry_controller_.reset();
externally_installed_app_prefs_.reset();
install_finalizer_.reset();
install_manager_.reset();
test_url_loader_ = nullptr;
file_utils_ = nullptr;
}
static std::string ParamInfoToString(
testing::TestParamInfo<WebAppInstallManagerTest::ParamType> info) {
switch (info.param) {
case SyncParam::kWithSync:
return "WithSync";
case SyncParam::kWithoutSync:
return "WithoutSync";
}
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
std::unique_ptr<FakeWebAppRegistryController> fake_registry_controller_;
std::unique_ptr<WebAppIconManager> icon_manager_;
std::unique_ptr<WebAppPolicyManager> policy_manager_;
std::unique_ptr<WebAppInstallManager> install_manager_;
std::unique_ptr<WebAppInstallFinalizer> install_finalizer_;
std::unique_ptr<FakeWebAppUiManager> ui_manager_;
std::unique_ptr<ExternallyInstalledWebAppPrefs>
externally_installed_app_prefs_;
// A weak ptr. The original is owned by install_manager_.
raw_ptr<TestWebAppUrlLoader> test_url_loader_ = nullptr;
scoped_refptr<TestFileUtils> file_utils_;
};
using WebAppInstallManagerTest_SyncOnly = WebAppInstallManagerTest;
TEST_P(WebAppInstallManagerTest_SyncOnly,
InstallWebAppsAfterSync_TwoConcurrentInstallsAreRunInOrder) {
url_loader().AddPrepareForLoadResults({WebAppUrlLoader::Result::kUrlLoaded,
WebAppUrlLoader::Result::kUrlLoaded});
const GURL url1{"https://example.com/path"};
const AppId app1_id = GenerateAppId(/*manifest_id=*/absl::nullopt, url1);
const GURL url2{"https://example.org/path"};
const AppId app2_id = GenerateAppId(/*manifest_id=*/absl::nullopt, url2);
{
std::unique_ptr<WebApp> app1 = CreateWebAppFromSyncAndPendingInstallation(
url1, "Name1 from sync", UserDisplayMode::kStandalone, SK_ColorRED,
/*is_locally_installed=*/false, /*scope=*/GURL(), /*icon_infos=*/{});
std::unique_ptr<WebApp> app2 = CreateWebAppFromSyncAndPendingInstallation(
url2, "Name2 from sync", UserDisplayMode::kBrowser, SK_ColorGREEN,
/*is_locally_installed=*/true, /*scope=*/GURL(), /*icon_infos=*/{});
Registry registry;
registry.emplace(app1_id, std::move(app1));
registry.emplace(app2_id, std::move(app2));
InitRegistrarWithRegistry(registry);
}
// 1 InstallTask == 1 DataRetriever, their lifetime matches.
base::flat_set<FakeDataRetriever*> task_data_retrievers;
base::RunLoop app1_installed_run_loop;
base::RunLoop app2_installed_run_loop;
enum class Event {
Task1_Queued,
Task2_Queued,
Task1_Started,
Task1_Completed,
App1_CallbackCalled,
Task2_Started,
Task2_Completed,
App2_CallbackCalled,
};
std::vector<Event> event_order;
int task_index = 0;
install_manager().SetDataRetrieverFactoryForTesting(
base::BindLambdaForTesting([&]() {
auto data_retriever = std::make_unique<FakeDataRetriever>();
task_index++;
GURL start_url = task_index == 1 ? url1 : url2;
data_retriever->BuildDefaultDataToRetrieve(start_url,
/*scope=*/start_url);
FakeDataRetriever* data_retriever_ptr = data_retriever.get();
task_data_retrievers.insert(data_retriever_ptr);
event_order.push_back(task_index == 1 ? Event::Task1_Queued
: Event::Task2_Queued);
// Every InstallTask starts with WebAppDataRetriever::GetIcons step.
data_retriever->SetGetIconsDelegate(base::BindLambdaForTesting(
[&, task_index](content::WebContents* web_contents,
const std::vector<GURL>& icon_urls,
bool skip_page_favicons) {
event_order.push_back(task_index == 1 ? Event::Task1_Started
: Event::Task2_Started);
IconsMap icons_map;
AddIconToIconsMap(GURL(kIconUrl), icon_size::k256, SK_ColorBLUE,
&icons_map);
return icons_map;
}));
// Every InstallTask ends with WebAppDataRetriever destructor.
data_retriever->SetDestructionCallback(
base::BindLambdaForTesting([&task_data_retrievers, &event_order,
data_retriever_ptr, task_index]() {
event_order.push_back(task_index == 1 ? Event::Task1_Completed
: Event::Task2_Completed);
task_data_retrievers.erase(data_retriever_ptr);
}));
return std::unique_ptr<WebAppDataRetriever>(std::move(data_retriever));
}));
EXPECT_FALSE(install_manager().has_web_contents_for_testing());
WebApp* web_app1 =
controller().mutable_registrar().GetAppByIdMutable(app1_id);
WebApp* web_app2 =
controller().mutable_registrar().GetAppByIdMutable(app2_id);
ASSERT_TRUE(web_app1);
ASSERT_TRUE(web_app2);
url_loader().SetNextLoadUrlResult(url1, WebAppUrlLoader::Result::kUrlLoaded);
url_loader().SetNextLoadUrlResult(url2, WebAppUrlLoader::Result::kUrlLoaded);
// Enqueue a request to install the 1st app.
install_manager().InstallWebAppsAfterSync(
{web_app1},
base::BindLambdaForTesting(
[&](const AppId& installed_app_id, webapps::InstallResultCode code) {
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, code);
EXPECT_EQ(app1_id, installed_app_id);
event_order.push_back(Event::App1_CallbackCalled);
app1_installed_run_loop.Quit();
}));
EXPECT_TRUE(install_manager().has_web_contents_for_testing());
EXPECT_EQ(0, GetNumFullyInstalledApps());
EXPECT_EQ(1u, task_data_retrievers.size());
// Immediately enqueue a request to install the 2nd app, WebContents is not
// ready.
install_manager().InstallWebAppsAfterSync(
{web_app2},
base::BindLambdaForTesting(
[&](const AppId& installed_app_id, webapps::InstallResultCode code) {
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, code);
EXPECT_EQ(app2_id, installed_app_id);
event_order.push_back(Event::App2_CallbackCalled);
app2_installed_run_loop.Quit();
}));
EXPECT_TRUE(install_manager().has_web_contents_for_testing());
EXPECT_EQ(2u, task_data_retrievers.size());
EXPECT_EQ(0, GetNumFullyInstalledApps());
// Wait for the 1st app installed.
app1_installed_run_loop.Run();
EXPECT_TRUE(install_manager().has_web_contents_for_testing());
EXPECT_EQ(1u, task_data_retrievers.size());
EXPECT_EQ(1, GetNumFullyInstalledApps());
// Wait for the 2nd app installed.
app2_installed_run_loop.Run();
EXPECT_FALSE(install_manager().has_web_contents_for_testing());
EXPECT_EQ(0u, task_data_retrievers.size());
EXPECT_EQ(2, GetNumFullyInstalledApps());
const std::vector<Event> expected_event_order{
Event::Task1_Queued, Event::Task2_Queued, Event::Task1_Started,
Event::Task1_Completed, Event::App1_CallbackCalled, Event::Task2_Started,
Event::Task2_Completed, Event::App2_CallbackCalled,
};
EXPECT_EQ(expected_event_order, event_order);
}
TEST_P(WebAppInstallManagerTest_SyncOnly,
InstallWebAppsAfterSync_InstallManagerDestroyed) {
const GURL start_url{"https://example.com/path"};
const AppId app_id = GenerateAppId(/*manifest_id=*/absl::nullopt, start_url);
{
std::unique_ptr<WebApp> app_in_sync_install =
CreateWebAppFromSyncAndPendingInstallation(
start_url, "Name from sync", UserDisplayMode::kStandalone,
SK_ColorRED,
/*is_locally_installed=*/true, /*scope=*/GURL(), /*icon_infos=*/{});
InitRegistrarWithApp(std::move(app_in_sync_install));
}
base::RunLoop run_loop;
install_manager().SetDataRetrieverFactoryForTesting(
base::BindLambdaForTesting([&]() {
auto data_retriever = std::make_unique<FakeDataRetriever>();
data_retriever->BuildDefaultDataToRetrieve(start_url,
/*scope=*/start_url);
// Every InstallTask starts with WebAppDataRetriever::GetIcons step.
data_retriever->SetGetIconsDelegate(base::BindLambdaForTesting(
[&](content::WebContents* web_contents,
const std::vector<GURL>& icon_urls, bool skip_page_favicons) {
run_loop.Quit();
IconsMap icons_map;
AddIconToIconsMap(GURL(kIconUrl), icon_size::k256, SK_ColorBLUE,
&icons_map);
return icons_map;
}));
return std::unique_ptr<WebAppDataRetriever>(std::move(data_retriever));
}));
WebApp* web_app = controller().mutable_registrar().GetAppByIdMutable(app_id);
url_loader().AddPrepareForLoadResults({WebAppUrlLoader::Result::kUrlLoaded});
url_loader().SetNextLoadUrlResult(start_url,
WebAppUrlLoader::Result::kUrlLoaded);
bool callback_called = false;
install_manager().InstallWebAppsAfterSync(
{web_app},
base::BindLambdaForTesting(
[&](const AppId& installed_app_id, webapps::InstallResultCode code) {
callback_called = true;
}));
EXPECT_TRUE(install_manager().has_web_contents_for_testing());
// Wait for the task to start.
run_loop.Run();
EXPECT_TRUE(install_manager().has_web_contents_for_testing());
// Simulate Profile getting destroyed.
DestroyManagers();
EXPECT_FALSE(callback_called);
}
TEST_P(WebAppInstallManagerTest_SyncOnly, InstallWebAppsAfterSync_Success) {
const std::string url_path{"https://example.com/path"};
const GURL url{url_path};
bool expect_locally_installed = AreAppsLocallyInstalledBySync();
const std::unique_ptr<WebApp> expected_app =
test::CreateWebApp(url, WebAppManagement::kSync);
expected_app->SetIsFromSyncAndPendingInstallation(false);
expected_app->SetScope(url);
expected_app->SetName("Name");
expected_app->SetIsLocallyInstalled(expect_locally_installed);
expected_app->SetInstallSourceForMetrics(webapps::WebappInstallSource::SYNC);
expected_app->SetDescription("Description");
expected_app->SetThemeColor(SK_ColorCYAN);
expected_app->SetDisplayMode(DisplayMode::kBrowser);
expected_app->SetUserDisplayMode(UserDisplayMode::kStandalone);
std::vector<apps::IconInfo> manifest_icons;
std::vector<int> sizes;
for (int size : SizesToGenerate()) {
apps::IconInfo icon_info;
icon_info.square_size_px = size;
icon_info.url =
GURL{url_path + "/icon" + base::NumberToString(size) + ".png"};
manifest_icons.push_back(std::move(icon_info));
sizes.push_back(size);
}
expected_app->SetManifestIcons(std::move(manifest_icons));
expected_app->SetDownloadedIconSizes(IconPurpose::ANY, std::move(sizes));
{
WebApp::SyncFallbackData sync_fallback_data;
sync_fallback_data.name = "Name";
sync_fallback_data.theme_color = SK_ColorCYAN;
sync_fallback_data.scope = url;
sync_fallback_data.icon_infos = expected_app->manifest_icons();
expected_app->SetSyncFallbackData(std::move(sync_fallback_data));
}
ASSERT_TRUE(expected_app->user_display_mode().has_value());
std::unique_ptr<const WebApp> app_in_sync_install =
CreateWebAppFromSyncAndPendingInstallation(
expected_app->start_url(), "Name from sync",
expected_app->user_display_mode(), SK_ColorRED,
expected_app->is_locally_installed(), expected_app->scope(),
expected_app->manifest_icons());
// Init using a copy.
InitRegistrarWithApp(std::make_unique<WebApp>(*app_in_sync_install));
WebApp* app = controller().mutable_registrar().GetAppByIdMutable(
expected_app->app_id());
url_loader().AddPrepareForLoadResults({WebAppUrlLoader::Result::kUrlLoaded});
url_loader().SetNextLoadUrlResult(url, WebAppUrlLoader::Result::kUrlLoaded);
install_manager().SetDataRetrieverFactoryForTesting(
base::BindLambdaForTesting([&expected_app]() {
return ConvertWebAppToDataRetriever(*expected_app);
}));
InstallResult result = InstallWebAppsAfterSync({app});
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, result.code);
EXPECT_EQ(app->app_id(), result.app_id);
EXPECT_EQ(1u, registrar().GetAppIds().size());
EXPECT_EQ(app, registrar().GetAppById(expected_app->app_id()));
EXPECT_NE(*app_in_sync_install, *app);
EXPECT_NE(app_in_sync_install->sync_fallback_data(),
app->sync_fallback_data());
EXPECT_EQ(*expected_app, *app);
}
TEST_P(WebAppInstallManagerTest_SyncOnly, InstallWebAppsAfterSync_Fallback) {
const GURL url{"https://example.com/path"};
bool expect_locally_installed = AreAppsLocallyInstalledBySync();
const std::unique_ptr<WebApp> expected_app =
test::CreateWebApp(url, WebAppManagement::kSync);
expected_app->SetIsFromSyncAndPendingInstallation(false);
expected_app->SetName("Name from sync");
expected_app->SetScope(url);
expected_app->SetDisplayMode(DisplayMode::kBrowser);
expected_app->SetUserDisplayMode(UserDisplayMode::kBrowser);
expected_app->SetIsLocallyInstalled(expect_locally_installed);
expected_app->SetInstallSourceForMetrics(webapps::WebappInstallSource::SYNC);
expected_app->SetThemeColor(SK_ColorRED);
// |scope| and |description| are empty here. |display_mode| is |kUndefined|.
std::vector<apps::IconInfo> manifest_icons;
std::vector<int> sizes;
for (int size : SizesToGenerate()) {
apps::IconInfo icon_info;
icon_info.square_size_px = size;
icon_info.url =
GURL{url.spec() + "/icon" + base::NumberToString(size) + ".png"};
manifest_icons.push_back(std::move(icon_info));
sizes.push_back(size);
}
expected_app->SetManifestIcons(std::move(manifest_icons));
expected_app->SetDownloadedIconSizes(IconPurpose::ANY, std::move(sizes));
expected_app->SetIsGeneratedIcon(true);
{
WebApp::SyncFallbackData sync_fallback_data;
sync_fallback_data.name = "Name from sync";
sync_fallback_data.theme_color = SK_ColorRED;
sync_fallback_data.scope = expected_app->scope();
sync_fallback_data.icon_infos = expected_app->manifest_icons();
expected_app->SetSyncFallbackData(std::move(sync_fallback_data));
}
std::unique_ptr<const WebApp> app_in_sync_install =
CreateWebAppFromSyncAndPendingInstallation(
expected_app->start_url(), expected_app->untranslated_name(),
expected_app->user_display_mode(),
expected_app->theme_color().value(),
expected_app->is_locally_installed(), expected_app->scope(),
expected_app->manifest_icons());
// Init using a copy.
InitRegistrarWithApp(std::make_unique<WebApp>(*app_in_sync_install));
WebApp* app = controller().mutable_registrar().GetAppByIdMutable(
expected_app->app_id());
// Simulate if the web app publisher's website is down.
url_loader().SetNextLoadUrlResult(
url, WebAppUrlLoader::Result::kFailedPageTookTooLong);
// about:blank will be loaded twice, one for the initial attempt and one for
// the fallback attempt.
url_loader().AddPrepareForLoadResults({WebAppUrlLoader::Result::kUrlLoaded,
WebAppUrlLoader::Result::kUrlLoaded});
install_manager().SetDataRetrieverFactoryForTesting(
base::BindLambdaForTesting([]() {
// The data retrieval stage must not be reached if url fails to load.
return CreateEmptyDataRetriever();
}));
InstallResult result = InstallWebAppsAfterSync({app});
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, result.code);
EXPECT_EQ(app->app_id(), result.app_id);
EXPECT_EQ(1u, registrar().GetAppIds().size());
EXPECT_EQ(app, registrar().GetAppById(expected_app->app_id()));
EXPECT_NE(*app_in_sync_install, *app);
EXPECT_EQ(app_in_sync_install->sync_fallback_data(),
app->sync_fallback_data());
EXPECT_EQ(*expected_app, *app);
}
TEST_P(WebAppInstallManagerTest_SyncOnly,
UninstallFromSyncAfterRegistryUpdate) {
std::unique_ptr<WebApp> app = test::CreateWebApp(
GURL("https://example.com/path"), WebAppManagement::kSync);
app->SetUserDisplayMode(UserDisplayMode::kStandalone);
const AppId app_id = app->app_id();
InitRegistrarWithApp(std::move(app));
file_utils().SetNextDeleteFileRecursivelyResult(true);
enum Event {
kUninstallWithoutRegistryUpdateFromSync,
kObserver_OnWebAppWillBeUninstalled,
kObserver_OnWebAppUninstalled,
kUninstallWithoutRegistryUpdateFromSync_Callback
};
std::vector<Event> event_order;
WebAppInstallManagerObserverAdapter observer(&install_manager());
observer.SetWebAppWillBeUninstalledDelegate(
base::BindLambdaForTesting([&](const AppId& uninstalled_app_id) {
EXPECT_EQ(uninstalled_app_id, app_id);
event_order.push_back(Event::kObserver_OnWebAppWillBeUninstalled);
}));
observer.SetWebAppUninstalledDelegate(
base::BindLambdaForTesting([&](const AppId& uninstalled_app_id) {
EXPECT_EQ(uninstalled_app_id, app_id);
event_order.push_back(Event::kObserver_OnWebAppUninstalled);
}));
base::RunLoop run_loop;
controller().SetUninstallWithoutRegistryUpdateFromSyncDelegate(
base::BindLambdaForTesting(
[&](const std::vector<AppId>& apps_to_uninstall,
SyncInstallDelegate::RepeatingUninstallCallback callback) {
ASSERT_FALSE(apps_to_uninstall.empty());
EXPECT_EQ(apps_to_uninstall[0], app_id);
event_order.push_back(
Event::kUninstallWithoutRegistryUpdateFromSync);
install_manager().UninstallWithoutRegistryUpdateFromSync(
std::move(apps_to_uninstall),
base::BindLambdaForTesting([&, callback](
const AppId& uninstalled_app_id,
bool uninstalled) {
EXPECT_EQ(uninstalled_app_id, app_id);
EXPECT_TRUE(uninstalled);
event_order.push_back(
Event::kUninstallWithoutRegistryUpdateFromSync_Callback);
run_loop.Quit();
callback.Run(uninstalled_app_id, uninstalled);
}));
}));
// The sync server sends a change to delete the app.
sync_bridge_test_utils::DeleteApps(controller().sync_bridge(), {app_id});
run_loop.Run();
const std::vector<Event> expected_event_order{
Event::kUninstallWithoutRegistryUpdateFromSync,
Event::kObserver_OnWebAppWillBeUninstalled,
Event::kObserver_OnWebAppUninstalled,
Event::kUninstallWithoutRegistryUpdateFromSync_Callback};
EXPECT_EQ(expected_event_order, event_order);
}
TEST_P(WebAppInstallManagerTest_SyncOnly,
UninstallFromSyncAfterRegistryUpdateInstallManagerObserver) {
std::unique_ptr<WebApp> app = test::CreateWebApp(
GURL("https://example.com/path"), WebAppManagement::kSync);
app->SetUserDisplayMode(UserDisplayMode::kStandalone);
const AppId app_id = app->app_id();
InitRegistrarWithApp(std::move(app));
file_utils().SetNextDeleteFileRecursivelyResult(true);
enum Event {
kUninstallWithoutRegistryUpdateFromSync,
kObserver_OnWebAppWillBeUninstalled,
kObserver_OnWebAppUninstalled,
kUninstallWithoutRegistryUpdateFromSync_Callback
};
std::vector<Event> event_order;
WebAppInstallManagerObserverAdapter observer(&install_manager());
observer.SetWebAppWillBeUninstalledDelegate(
base::BindLambdaForTesting([&](const AppId& uninstalled_app_id) {
EXPECT_EQ(uninstalled_app_id, app_id);
event_order.push_back(Event::kObserver_OnWebAppWillBeUninstalled);
}));
observer.SetWebAppUninstalledDelegate(
base::BindLambdaForTesting([&](const AppId& uninstalled_app_id) {
EXPECT_EQ(uninstalled_app_id, app_id);
event_order.push_back(Event::kObserver_OnWebAppUninstalled);
}));
base::RunLoop run_loop;
controller().SetUninstallWithoutRegistryUpdateFromSyncDelegate(
base::BindLambdaForTesting(
[&](const std::vector<AppId>& apps_to_uninstall,
SyncInstallDelegate::RepeatingUninstallCallback callback) {
ASSERT_FALSE(apps_to_uninstall.empty());
EXPECT_EQ(apps_to_uninstall[0], app_id);
event_order.push_back(
Event::kUninstallWithoutRegistryUpdateFromSync);
install_manager().UninstallWithoutRegistryUpdateFromSync(
std::move(apps_to_uninstall),
base::BindLambdaForTesting([&, callback](
const AppId& uninstalled_app_id,
bool uninstalled) {
EXPECT_EQ(uninstalled_app_id, app_id);
EXPECT_TRUE(uninstalled);
event_order.push_back(
Event::kUninstallWithoutRegistryUpdateFromSync_Callback);
run_loop.Quit();
callback.Run(uninstalled_app_id, uninstalled);
}));
}));
// The sync server sends a change to delete the app.
sync_bridge_test_utils::DeleteApps(controller().sync_bridge(), {app_id});
run_loop.Run();
const std::vector<Event> expected_event_order{
Event::kUninstallWithoutRegistryUpdateFromSync,
Event::kObserver_OnWebAppWillBeUninstalled,
Event::kObserver_OnWebAppUninstalled,
Event::kUninstallWithoutRegistryUpdateFromSync_Callback};
EXPECT_EQ(expected_event_order, event_order);
}
TEST_P(WebAppInstallManagerTest_SyncOnly,
PolicyAndUser_UninstallExternalWebApp) {
std::unique_ptr<WebApp> policy_and_user_app = test::CreateWebApp(
GURL("https://example.com/path"), WebAppManagement::kSync);
policy_and_user_app->AddSource(WebAppManagement::kPolicy);
policy_and_user_app->SetUserDisplayMode(UserDisplayMode::kStandalone);
const AppId app_id = policy_and_user_app->app_id();
const GURL external_app_url("https://example.com/path/policy");
externally_installed_app_prefs().Insert(
external_app_url, app_id, ExternalInstallSource::kExternalPolicy);
InitRegistrarWithApp(std::move(policy_and_user_app));
EXPECT_FALSE(finalizer().WasPreinstalledWebAppUninstalled(app_id));
bool observer_uninstall_called = false;
WebAppInstallManagerObserverAdapter observer(&install_manager());
observer.SetWebAppUninstalledDelegate(
base::BindLambdaForTesting([&](const AppId& uninstalled_app_id) {
observer_uninstall_called = true;
}));
// Unknown url fails.
EXPECT_EQ(webapps::UninstallResultCode::kNoAppToUninstall,
UninstallPolicyWebAppByUrl(GURL("https://example.org/")));
// Uninstall policy app first.
EXPECT_EQ(webapps::UninstallResultCode::kSuccess,
UninstallPolicyWebAppByUrl(external_app_url));
EXPECT_TRUE(registrar().GetAppById(app_id));
EXPECT_FALSE(observer_uninstall_called);
EXPECT_FALSE(finalizer().WasPreinstalledWebAppUninstalled(app_id));
EXPECT_TRUE(finalizer().CanUserUninstallWebApp(app_id));
}
TEST_P(WebAppInstallManagerTest_SyncOnly,
PolicyAndUser_UninstallExternalWebAppInstallManagerObserver) {
std::unique_ptr<WebApp> policy_and_user_app = test::CreateWebApp(
GURL("https://example.com/path"), WebAppManagement::kSync);
policy_and_user_app->AddSource(WebAppManagement::kPolicy);
policy_and_user_app->SetUserDisplayMode(UserDisplayMode::kStandalone);
const AppId app_id = policy_and_user_app->app_id();
const GURL external_app_url("https://example.com/path/policy");
externally_installed_app_prefs().Insert(
external_app_url, app_id, ExternalInstallSource::kExternalPolicy);
InitRegistrarWithApp(std::move(policy_and_user_app));
EXPECT_FALSE(finalizer().WasPreinstalledWebAppUninstalled(app_id));
bool observer_uninstall_called = false;
WebAppInstallManagerObserverAdapter observer(&install_manager());
observer.SetWebAppUninstalledDelegate(
base::BindLambdaForTesting([&](const AppId& uninstalled_app_id) {
observer_uninstall_called = true;
}));
// Unknown url fails.
EXPECT_EQ(webapps::UninstallResultCode::kNoAppToUninstall,
UninstallPolicyWebAppByUrl(GURL("https://example.org/")));
// Uninstall policy app first.
EXPECT_EQ(webapps::UninstallResultCode::kSuccess,
UninstallPolicyWebAppByUrl(external_app_url));
EXPECT_TRUE(registrar().GetAppById(app_id));
EXPECT_FALSE(observer_uninstall_called);
EXPECT_FALSE(finalizer().WasPreinstalledWebAppUninstalled(app_id));
EXPECT_TRUE(finalizer().CanUserUninstallWebApp(app_id));
}
TEST_P(WebAppInstallManagerTest_SyncOnly, DefaultAndUser_UninstallWebApp) {
std::unique_ptr<WebApp> default_and_user_app = test::CreateWebApp(
GURL("https://example.com/path"), WebAppManagement::kSync);
default_and_user_app->AddSource(WebAppManagement::kDefault);
default_and_user_app->SetUserDisplayMode(UserDisplayMode::kStandalone);
const AppId app_id = default_and_user_app->app_id();
const GURL external_app_url("https://example.com/path/default");
externally_installed_app_prefs().Insert(
external_app_url, app_id, ExternalInstallSource::kExternalDefault);
InitRegistrarWithApp(std::move(default_and_user_app));
EXPECT_TRUE(finalizer().CanUserUninstallWebApp(app_id));
EXPECT_FALSE(finalizer().WasPreinstalledWebAppUninstalled(app_id));
WebAppInstallManagerObserverAdapter observer(&install_manager());
bool observer_uninstalled_called = false;
observer.SetWebAppUninstalledDelegate(
base::BindLambdaForTesting([&](const AppId& uninstalled_app_id) {
EXPECT_EQ(app_id, uninstalled_app_id);
observer_uninstalled_called = true;
}));
file_utils().SetNextDeleteFileRecursivelyResult(true);
EXPECT_EQ(webapps::UninstallResultCode::kSuccess, UninstallWebApp(app_id));
EXPECT_FALSE(registrar().GetAppById(app_id));
EXPECT_TRUE(observer_uninstalled_called);
EXPECT_FALSE(finalizer().CanUserUninstallWebApp(app_id));
EXPECT_TRUE(finalizer().WasPreinstalledWebAppUninstalled(app_id));
}
TEST_P(WebAppInstallManagerTest_SyncOnly,
DefaultAndUser_UninstallWebAppInstallManagerObserver) {
std::unique_ptr<WebApp> default_and_user_app = test::CreateWebApp(
GURL("https://example.com/path"), WebAppManagement::kSync);
default_and_user_app->AddSource(WebAppManagement::kDefault);
default_and_user_app->SetUserDisplayMode(UserDisplayMode::kStandalone);
const AppId app_id = default_and_user_app->app_id();
const GURL external_app_url("https://example.com/path/default");
externally_installed_app_prefs().Insert(
external_app_url, app_id, ExternalInstallSource::kExternalDefault);
InitRegistrarWithApp(std::move(default_and_user_app));
EXPECT_TRUE(finalizer().CanUserUninstallWebApp(app_id));
EXPECT_FALSE(finalizer().WasPreinstalledWebAppUninstalled(app_id));
WebAppInstallManagerObserverAdapter observer(&install_manager());
bool observer_uninstalled_called = false;
observer.SetWebAppUninstalledDelegate(
base::BindLambdaForTesting([&](const AppId& uninstalled_app_id) {
EXPECT_EQ(app_id, uninstalled_app_id);
observer_uninstalled_called = true;
}));
file_utils().SetNextDeleteFileRecursivelyResult(true);
EXPECT_EQ(webapps::UninstallResultCode::kSuccess, UninstallWebApp(app_id));
EXPECT_FALSE(registrar().GetAppById(app_id));
EXPECT_TRUE(observer_uninstalled_called);
EXPECT_FALSE(finalizer().CanUserUninstallWebApp(app_id));
EXPECT_TRUE(finalizer().WasPreinstalledWebAppUninstalled(app_id));
}
TEST_P(WebAppInstallManagerTest, InstallWebAppFromInfo) {
InitEmptyRegistrar();
const GURL url("https://example.com/path");
const AppId expected_app_id =
GenerateAppId(/*manifest_id=*/absl::nullopt, url);
auto server_web_app_info = std::make_unique<WebAppInstallInfo>();
server_web_app_info->start_url = url;
server_web_app_info->scope = url;
server_web_app_info->title = u"Test web app";
server_web_app_info->install_url = url;
const webapps::WebappInstallSource install_source =
AreSystemWebAppsSupported()
? webapps::WebappInstallSource::SYSTEM_DEFAULT
: webapps::WebappInstallSource::OMNIBOX_INSTALL_ICON;
InstallResult result = InstallWebAppFromInfo(
std::move(server_web_app_info),
/*overwrite_existing_manifest_fields=*/false, install_source);
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, result.code);
EXPECT_EQ(expected_app_id, result.app_id);
const WebApp* web_app = registrar().GetAppById(expected_app_id);
ASSERT_TRUE(web_app);
std::map<SquareSizePx, SkBitmap> icon_bitmaps =
ReadIcons(expected_app_id, IconPurpose::ANY,
web_app->downloaded_icon_sizes(IconPurpose::ANY));
// Make sure that icons have been generated for all sub sizes.
EXPECT_TRUE(ContainsOneIconOfEachSize(icon_bitmaps));
}
TEST_P(WebAppInstallManagerTest, TaskQueueWebContentsReadyRace) {
InitEmptyRegistrar();
std::unique_ptr<WebAppInstallTask> task_a = CreateDummyTask();
WebAppInstallTask* task_a_ptr = task_a.get();
std::unique_ptr<WebAppInstallTask> task_b = CreateDummyTask();
std::unique_ptr<WebAppInstallTask> task_c = CreateDummyTask();
// Enqueue task A and await it to be started.
base::RunLoop run_loop_a_start;
url_loader().SetPrepareForLoadResultLoaded();
install_manager().EnsureWebContentsCreated();
install_manager().EnqueueTask(std::move(task_a),
run_loop_a_start.QuitClosure());
run_loop_a_start.Run();
// Enqueue task B before A has finished.
bool task_b_started = false;
install_manager().EnqueueTask(
std::move(task_b),
base::BindLambdaForTesting([&]() { task_b_started = true; }));
// Finish task A.
url_loader().SetPrepareForLoadResultLoaded();
install_manager().OnQueuedTaskCompleted(
task_a_ptr, base::DoNothing(), AppId(),
webapps::InstallResultCode::kSuccessNewInstall);
// Task B needs to wait for WebContents to return ready.
EXPECT_FALSE(task_b_started);
// Enqueue task C before B has started.
bool task_c_started = false;
install_manager().EnqueueTask(
std::move(task_c),
base::BindLambdaForTesting([&]() { task_c_started = true; }));
// Task C should not start before B has started.
EXPECT_FALSE(task_b_started);
EXPECT_FALSE(task_c_started);
}
TEST_P(WebAppInstallManagerTest_SyncOnly,
InstallWebAppFromManifestWithFallback_OverwriteIsLocallyInstalled) {
const GURL start_url{"https://example.com/path"};
const AppId app_id = GenerateAppId(/*manifest_id=*/absl::nullopt, start_url);
{
std::unique_ptr<WebApp> app_in_sync_install =
CreateWebAppFromSyncAndPendingInstallation(
start_url, "Name from sync",
/*user_display_mode=*/UserDisplayMode::kStandalone, SK_ColorRED,
/*is_locally_installed=*/false, /*scope=*/GURL(),
/*icon_infos=*/{});
InitRegistrarWithApp(std::move(app_in_sync_install));
}
EXPECT_FALSE(registrar().IsLocallyInstalled(app_id));
EXPECT_EQ(DisplayMode::kBrowser,
registrar().GetAppEffectiveDisplayMode(app_id));
// DefaultDataRetriever returns DisplayMode::kStandalone app's display mode.
UseDefaultDataRetriever(start_url);
InstallResult result = InstallWebAppFromManifestWithFallback();
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, result.code);
EXPECT_EQ(app_id, result.app_id);
EXPECT_TRUE(registrar().IsInstalled(app_id));
EXPECT_TRUE(registrar().IsLocallyInstalled(app_id));
// InstallWebAppFromManifestWithFallback sets user_display_mode to kBrowser
// because TestAcceptDialogCallback doesn't set open_as_window to true.
EXPECT_EQ(DisplayMode::kBrowser,
registrar().GetAppEffectiveDisplayMode(app_id));
}
TEST_P(WebAppInstallManagerTest_SyncOnly,
InstallWebAppFromWebAppStoreThenInstallFromSync) {
InitEmptyRegistrar();
const GURL start_url("https://example.com/path");
const AppId app_id = GenerateAppId(/*manifest_id=*/absl::nullopt, start_url);
// Reproduces `ApkWebAppInstaller` install parameters.
auto apk_install_info = std::make_unique<WebAppInstallInfo>();
apk_install_info->start_url = start_url;
apk_install_info->scope = GURL("https://example.com/apk_scope");
apk_install_info->title = u"Name from APK";
apk_install_info->theme_color = SK_ColorWHITE;
apk_install_info->display_mode = DisplayMode::kStandalone;
apk_install_info->user_display_mode = UserDisplayMode::kStandalone;
AddGeneratedIcon(&apk_install_info->icon_bitmaps.any, icon_size::k128,
SK_ColorYELLOW);
InstallResult result =
InstallWebAppFromInfo(std::move(apk_install_info),
/*overwrite_existing_manifest_fields=*/false,
webapps::WebappInstallSource::ARC);
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, result.code);
EXPECT_EQ(app_id, result.app_id);
const WebApp* web_app = registrar().GetAppById(app_id);
ASSERT_TRUE(web_app);
EXPECT_TRUE(web_app->IsWebAppStoreInstalledApp());
EXPECT_FALSE(web_app->IsSynced());
EXPECT_FALSE(web_app->is_from_sync_and_pending_installation());
ASSERT_TRUE(web_app->theme_color().has_value());
EXPECT_EQ(SK_ColorWHITE, web_app->theme_color().value());
EXPECT_EQ("Name from APK", web_app->untranslated_name());
EXPECT_EQ("https://example.com/apk_scope", web_app->scope().spec());
ASSERT_TRUE(web_app->sync_fallback_data().theme_color.has_value());
EXPECT_EQ(SK_ColorWHITE, web_app->sync_fallback_data().theme_color.value());
EXPECT_EQ("Name from APK", web_app->sync_fallback_data().name);
EXPECT_EQ("https://example.com/apk_scope",
web_app->sync_fallback_data().scope.spec());
EXPECT_EQ(DisplayMode::kStandalone, web_app->display_mode());
EXPECT_EQ(UserDisplayMode::kStandalone, web_app->user_display_mode());
EXPECT_TRUE(web_app->manifest_icons().empty());
EXPECT_TRUE(web_app->sync_fallback_data().icon_infos.empty());
EXPECT_EQ(SK_ColorYELLOW, IconManagerReadAppIconPixel(icon_manager(), app_id,
icon_size::k128));
// Simulates the same web app arriving from sync.
{
auto synced_specifics_data = std::make_unique<WebApp>(app_id);
synced_specifics_data->SetStartUrl(start_url);
synced_specifics_data->AddSource(WebAppManagement::kSync);
synced_specifics_data->SetUserDisplayMode(UserDisplayMode::kBrowser);
synced_specifics_data->SetName("Name From Sync");
WebApp::SyncFallbackData sync_fallback_data;
sync_fallback_data.name = "Name From Sync";
sync_fallback_data.theme_color = SK_ColorMAGENTA;
sync_fallback_data.scope = GURL("https://example.com/sync_scope");
apps::IconInfo apps_icon_info = CreateIconInfo(
/*icon_base_url=*/start_url, IconPurpose::MONOCHROME, icon_size::k64);
sync_fallback_data.icon_infos.push_back(std::move(apps_icon_info));
synced_specifics_data->SetSyncFallbackData(std::move(sync_fallback_data));
// `SyncInstallDelegate::InstallWebAppsAfterSync()` must not be called.
controller().SetInstallWebAppsAfterSyncDelegate(base::BindLambdaForTesting(
[&](std::vector<WebApp*> apps_to_install,
FakeWebAppRegistryController::RepeatingInstallCallback callback) {
ADD_FAILURE();
}));
std::vector<std::unique_ptr<WebApp>> add_synced_apps_data;
add_synced_apps_data.push_back(std::move(synced_specifics_data));
sync_bridge_test_utils::AddApps(controller().sync_bridge(),
add_synced_apps_data);
}
EXPECT_EQ(web_app, registrar().GetAppById(app_id));
EXPECT_TRUE(web_app->IsWebAppStoreInstalledApp());
EXPECT_TRUE(web_app->IsSynced());
EXPECT_FALSE(web_app->is_from_sync_and_pending_installation());
EXPECT_EQ(DisplayMode::kStandalone, web_app->display_mode());
EXPECT_EQ(UserDisplayMode::kBrowser, web_app->user_display_mode());
ASSERT_TRUE(web_app->theme_color().has_value());
EXPECT_EQ(SK_ColorWHITE, web_app->theme_color().value());
EXPECT_EQ("Name from APK", web_app->untranslated_name());
EXPECT_EQ("https://example.com/apk_scope", web_app->scope().spec());
ASSERT_TRUE(web_app->sync_fallback_data().theme_color.has_value());
EXPECT_EQ(SK_ColorMAGENTA, web_app->sync_fallback_data().theme_color.value());
EXPECT_EQ("Name From Sync", web_app->sync_fallback_data().name);
EXPECT_EQ("https://example.com/sync_scope",
web_app->sync_fallback_data().scope.spec());
EXPECT_TRUE(web_app->manifest_icons().empty());
ASSERT_EQ(1u, web_app->sync_fallback_data().icon_infos.size());
const apps::IconInfo& app_icon_info =
web_app->sync_fallback_data().icon_infos[0];
EXPECT_EQ(apps::IconInfo::Purpose::kMonochrome, app_icon_info.purpose);
EXPECT_EQ(icon_size::k64, app_icon_info.square_size_px);
EXPECT_EQ("https://example.com/icon-64.png", app_icon_info.url.spec());
EXPECT_EQ(SK_ColorYELLOW, IconManagerReadAppIconPixel(icon_manager(), app_id,
icon_size::k128));
}
TEST_P(WebAppInstallManagerTest_SyncOnly, InstallSubApp) {
const GURL parent_url{"https://example.com/parent"};
const AppId parent_app_id =
GenerateAppId(/*manifest_id=*/absl::nullopt, parent_url);
const GURL install_url{"https://example.com/sub/app"};
const AppId app_id =
GenerateAppId(/*manifest_id=*/absl::nullopt, install_url);
const GURL second_install_url{"https://example.com/sub/second_app"};
const AppId second_app_id =
GenerateAppId(/*manifest_id=*/absl::nullopt, second_install_url);
InitEmptyRegistrar();
// Install a sub-app and verify a bunch of things.
InstallResult result = InstallSubApp(parent_app_id, install_url);
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, result.code);
EXPECT_EQ(app_id, result.app_id);
EXPECT_TRUE(registrar().IsInstalled(app_id));
EXPECT_TRUE(registrar().IsLocallyInstalled(app_id));
EXPECT_EQ(DisplayMode::kStandalone,
registrar().GetAppEffectiveDisplayMode(app_id));
const WebApp* app = registrar().GetAppById(app_id);
EXPECT_EQ(parent_app_id, app->parent_app_id());
EXPECT_TRUE(app->IsSubAppInstalledApp());
EXPECT_TRUE(app->CanUserUninstallWebApp());
// One sub-app.
EXPECT_EQ(1ul, registrar().GetAllSubAppIds(parent_app_id).size());
// Check that we get |kSuccessAlreadyInstalled| if we try installing the same
// app again.
EXPECT_EQ(webapps::InstallResultCode::kSuccessAlreadyInstalled,
InstallSubApp(parent_app_id, install_url).code);
// Still one sub-app.
EXPECT_EQ(1ul, registrar().GetAllSubAppIds(parent_app_id).size());
// Install a different sub-app and verify count equals 2.
result = InstallSubApp(parent_app_id, second_install_url);
EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall, result.code);
EXPECT_EQ(second_app_id, result.app_id);
// Two sub-apps.
EXPECT_EQ(2ul, registrar().GetAllSubAppIds(parent_app_id).size());
}
INSTANTIATE_TEST_SUITE_P(All,
WebAppInstallManagerTest,
#if BUILDFLAG(IS_CHROMEOS_ASH)
::testing::Values(SyncParam::kWithoutSync,
SyncParam::kWithSync),
#else
::testing::Values(SyncParam::kWithSync),
#endif
WebAppInstallManagerTest::ParamInfoToString);
INSTANTIATE_TEST_SUITE_P(All,
WebAppInstallManagerTest_SyncOnly,
::testing::Values(SyncParam::kWithSync),
WebAppInstallManagerTest::ParamInfoToString);
} // namespace web_app