blob: 386e1ce3c49396998810cd0b69a2885e44fe4590 [file] [log] [blame]
// Copyright 2019 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/web_app_install_manager.h"
#include <initializer_list>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/check.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/traits_bag.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/web_applications/commands/web_app_command.h"
#include "chrome/browser/web_applications/commands/web_app_uninstall_command.h"
#include "chrome/browser/web_applications/test/fake_data_retriever.h"
#include "chrome/browser/web_applications/test/fake_web_app_provider.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_install_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/user_uninstalled_preinstalled_web_app_prefs.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/browser/web_applications/web_app_command_manager.h"
#include "chrome/browser/web_applications/web_app_command_scheduler.h"
#include "chrome/browser/web_applications/web_app_data_retriever.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_provider.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/browser/web_applications/web_app_registry_update.h"
#include "chrome/browser/web_applications/web_app_sync_bridge.h"
#include "chrome/test/base/testing_profile.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_metrics.h"
#include "components/webapps/browser/uninstall_result_code.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/skia/include/core/SkBitmap.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 {
std::unique_ptr<WebAppInstallTask> CreateDummyTask() {
return std::make_unique<WebAppInstallTask>(
/*profile=*/nullptr,
/*install_finalizer=*/nullptr,
/*data_retriever=*/nullptr,
/*registrar=*/nullptr, webapps::WebappInstallSource::EXTERNAL_DEFAULT);
}
// 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();
auto* fake_provider = web_app::FakeWebAppProvider::Get(profile());
file_utils_ = base::MakeRefCounted<TestFileUtils>();
fake_provider->SetIconManager(
std::make_unique<WebAppIconManager>(profile(), file_utils_));
test::AwaitStartWebAppProviderAndSubsystems(profile());
provider().sync_bridge().set_disable_checks_for_testing(true);
auto test_url_loader = std::make_unique<TestWebAppUrlLoader>();
test_url_loader_ = test_url_loader.get();
provider().install_manager().SetUrlLoaderForTesting(
std::move(test_url_loader));
}
void TearDown() override {
DestroyManagers();
WebAppTest::TearDown();
}
WebAppRegistrar& registrar() { return provider().registrar(); }
WebAppCommandManager& command_manager() {
return provider().command_manager();
}
WebAppInstallManager& install_manager() {
return provider().install_manager();
}
WebAppInstallFinalizer& finalizer() { return provider().install_finalizer(); }
WebAppIconManager& icon_manager() { return provider().icon_manager(); }
TestWebAppUrlLoader& url_loader() { return *test_url_loader_; }
TestFileUtils& file_utils() {
DCHECK(file_utils_);
return *file_utils_;
}
WebAppProvider& provider() { return *WebAppProvider::GetForTest(profile()); }
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;
}
AppId InitRegistrarWithApp(std::unique_ptr<WebApp> app) {
DCHECK(registrar().is_empty());
const AppId& app_id = app->app_id();
{
ScopedRegistryUpdate update(&provider().sync_bridge());
update->CreateApp(std::move(app));
}
return app_id;
}
struct InstallResult {
AppId app_id;
webapps::InstallResultCode code;
};
InstallResult InstallWebAppFromInfo(
std::unique_ptr<WebAppInstallInfo> install_info,
bool overwrite_existing_manifest_fields,
webapps::WebappInstallSource install_source) {
InstallResult result;
base::RunLoop run_loop;
provider().scheduler().InstallFromInfo(
std::move(install_info), overwrite_existing_manifest_fields,
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() {
int num_apps = 0;
for ([[maybe_unused]] const WebApp& app : registrar().GetApps()) {
++num_apps;
}
return num_apps;
}
webapps::UninstallResultCode UninstallPolicyWebAppByUrl(const GURL& app_url) {
absl::optional<AppId> app_id =
provider().registrar().LookupExternalAppId(app_url);
if (!app_id.has_value()) {
return webapps::UninstallResultCode::kNoAppToUninstall;
}
webapps::UninstallResultCode result;
base::RunLoop run_loop;
auto uninstall_command = std::make_unique<WebAppUninstallCommand>(
app_id.value(), WebAppManagement::kPolicy,
webapps::WebappUninstallSource::kExternalPolicy,
base::BindLambdaForTesting([&](webapps::UninstallResultCode code) {
result = code;
run_loop.Quit();
}),
profile());
uninstall_command->SetRemoveManagementTypeCallbackForTesting(
base::BindLambdaForTesting([&](const AppId& app_id) {
// On removing the policy source, the web app can now be user
// uninstalled.
EXPECT_TRUE(finalizer().CanUserUninstallWebApp(app_id));
}));
command_manager().ScheduleCommand(std::move(uninstall_command));
run_loop.Run();
return result;
}
webapps::UninstallResultCode UninstallWebApp(const AppId& app_id) {
webapps::UninstallResultCode result;
base::RunLoop run_loop;
auto uninstall_command = std::make_unique<WebAppUninstallCommand>(
app_id, /*management_source=*/absl::nullopt,
webapps::WebappUninstallSource::kAppMenu,
base::BindLambdaForTesting([&](webapps::UninstallResultCode code) {
result = code;
run_loop.Quit();
}),
profile());
command_manager().ScheduleCommand(std::move(uninstall_command));
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));
}));
}
bool WasPreinstalledWebAppUninstalled(const AppId app_id) {
return UserUninstalledPreinstalledWebAppPrefs(profile()->GetPrefs())
.DoesAppIdExist(app_id);
}
void DestroyManagers() {
provider().Shutdown();
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_;
raw_ptr<TestWebAppUrlLoader> test_url_loader_ = nullptr;
scoped_refptr<TestFileUtils> file_utils_;
};
using WebAppInstallManagerTest_SyncOnly = WebAppInstallManagerTest;
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 {
kUninstallFromSync,
kObserver_OnWebAppWillBeUninstalled,
kObserver_OnWebAppUninstalled,
kUninstallFromSync_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;
install_manager().SetUninstallCallbackForTesting(base::BindLambdaForTesting(
[&](const AppId& uninstalled_app_id, webapps::UninstallResultCode code) {
EXPECT_EQ(uninstalled_app_id, app_id);
EXPECT_EQ(code, webapps::UninstallResultCode::kSuccess);
event_order.push_back(Event::kUninstallFromSync_Callback);
run_loop.Quit();
}));
// The sync server sends a change to delete the app.
sync_bridge_test_utils::DeleteApps(provider().sync_bridge(), {app_id});
event_order.push_back(Event::kUninstallFromSync);
run_loop.Run();
const std::vector<Event> expected_event_order{
Event::kUninstallFromSync, Event::kObserver_OnWebAppWillBeUninstalled,
Event::kObserver_OnWebAppUninstalled, Event::kUninstallFromSync_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 {
kUninstallFromSync,
kObserver_OnWebAppWillBeUninstalled,
kObserver_OnWebAppUninstalled,
kUninstallFromSync_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;
install_manager().SetUninstallCallbackForTesting(base::BindLambdaForTesting(
[&](const AppId& uninstalled_app_id, webapps::UninstallResultCode code) {
EXPECT_EQ(uninstalled_app_id, app_id);
EXPECT_EQ(code, webapps::UninstallResultCode::kSuccess);
event_order.push_back(Event::kUninstallFromSync_Callback);
run_loop.Quit();
}));
// The sync server sends a change to delete the app.
sync_bridge_test_utils::DeleteApps(provider().sync_bridge(), {app_id});
event_order.push_back(Event::kUninstallFromSync);
run_loop.Run();
const std::vector<Event> expected_event_order{
Event::kUninstallFromSync, Event::kObserver_OnWebAppWillBeUninstalled,
Event::kObserver_OnWebAppUninstalled, Event::kUninstallFromSync_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");
InitRegistrarWithApp(std::move(policy_and_user_app));
test::AddInstallUrlData(profile()->GetPrefs(), &provider().sync_bridge(),
app_id, external_app_url,
ExternalInstallSource::kExternalPolicy);
EXPECT_FALSE(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(WasPreinstalledWebAppUninstalled(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");
InitRegistrarWithApp(std::move(policy_and_user_app));
test::AddInstallUrlData(profile()->GetPrefs(), &provider().sync_bridge(),
app_id, external_app_url,
ExternalInstallSource::kExternalPolicy);
EXPECT_FALSE(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(WasPreinstalledWebAppUninstalled(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);
default_and_user_app->AddInstallURLToManagementExternalConfigMap(
WebAppManagement::kDefault, GURL("https://example.com/path"));
const AppId app_id = default_and_user_app->app_id();
const GURL external_app_url("https://example.com/path/default");
InitRegistrarWithApp(std::move(default_and_user_app));
test::AddInstallUrlData(profile()->GetPrefs(), &provider().sync_bridge(),
app_id, external_app_url,
ExternalInstallSource::kExternalDefault);
EXPECT_TRUE(finalizer().CanUserUninstallWebApp(app_id));
EXPECT_FALSE(WasPreinstalledWebAppUninstalled(app_id));
EXPECT_TRUE(registrar().IsActivelyInstalled(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(WasPreinstalledWebAppUninstalled(app_id));
EXPECT_FALSE(registrar().IsActivelyInstalled(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);
default_and_user_app->AddInstallURLToManagementExternalConfigMap(
WebAppManagement::kDefault, GURL("https://example.com/path"));
const AppId app_id = default_and_user_app->app_id();
const GURL external_app_url("https://example.com/path/default");
InitRegistrarWithApp(std::move(default_and_user_app));
test::AddInstallUrlData(profile()->GetPrefs(), &provider().sync_bridge(),
app_id, external_app_url,
ExternalInstallSource::kExternalDefault);
EXPECT_TRUE(finalizer().CanUserUninstallWebApp(app_id));
EXPECT_FALSE(WasPreinstalledWebAppUninstalled(app_id));
EXPECT_TRUE(registrar().IsActivelyInstalled(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(WasPreinstalledWebAppUninstalled(app_id));
EXPECT_FALSE(registrar().IsActivelyInstalled(app_id));
}
TEST_P(WebAppInstallManagerTest, TaskQueueWebContentsReadyRace) {
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,
InstallWebAppFromWebAppStoreThenInstallFromSync) {
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));
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(provider().sync_bridge(),
add_synced_apps_data);
// No apps installs should be triggered.
EXPECT_THAT(registrar().GetAppsFromSyncAndPendingInstallation(),
testing::IsEmpty());
}
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());
EXPECT_TRUE(registrar().IsActivelyInstalled(app_id));
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));
}
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