blob: 68037bb116419a683e9f3af0b23ce49bd7e3e653 [file] [log] [blame]
// Copyright 2020 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/test/web_app_test_utils.h"
#include <algorithm>
#include <cstdint>
#include <ostream>
#include <random>
#include <set>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/functional/bind.h"
#include "base/json/json_reader.h"
#include "base/location.h"
#include "base/memory/scoped_refptr.h"
#include "base/numerics/clamped_math.h"
#include "base/numerics/safe_conversions.h"
#include "base/ranges/algorithm.h"
#include "base/run_loop.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_dialogs.h"
#include "chrome/browser/web_applications/externally_installed_web_app_prefs.h"
#include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_location.h"
#include "chrome/browser/web_applications/mojom/user_display_mode.mojom.h"
#include "chrome/browser/web_applications/proto/web_app_os_integration_state.pb.h"
#include "chrome/browser/web_applications/test/web_app_test_observers.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/browser/web_applications/web_app_chromeos_data.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_install_info.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_registry_update.h"
#include "chrome/browser/web_applications/web_app_sync_bridge.h"
#include "chrome/browser/web_applications/web_app_utils.h"
#include "chrome/common/pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/services/app_service/public/cpp/file_handler.h"
#include "components/services/app_service/public/cpp/icon_info.h"
#include "components/services/app_service/public/cpp/protocol_handler_info.h"
#include "components/services/app_service/public/cpp/share_target.h"
#include "components/services/app_service/public/cpp/url_handler_info.h"
#include "components/sync/base/time.h"
#include "components/webapps/browser/installable/installable_metrics.h"
#include "content/public/browser/service_worker_context.h"
#include "content/public/browser/storage_partition.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/abseil-cpp/absl/types/variant.h"
#include "third_party/blink/public/common/manifest/manifest.h"
#include "third_party/blink/public/common/permissions_policy/origin_with_possible_wildcards.h"
#include "third_party/blink/public/common/permissions_policy/permissions_policy.h"
#include "third_party/blink/public/common/permissions_policy/policy_helper_public.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/blink/public/mojom/manifest/capture_links.mojom-shared.h"
#include "third_party/skia/include/core/SkColor.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace {
std::vector<std::string> features = {
"default_on_feature", "default_self_feature", "default_disabled_feature"};
} // namespace
namespace web_app {
namespace test {
namespace {
class RandomHelper {
public:
explicit RandomHelper(const uint32_t seed)
: // Seed of 0 and 1 generate the same sequence, so skip 0.
generator_(seed + 1),
distribution_(0u, UINT32_MAX) {}
uint32_t next_uint() { return distribution_(generator_); }
// Return an unsigned int between 0 (inclusive) and bound (exclusive).
uint32_t next_uint(uint32_t bound) { return next_uint() % bound; }
bool next_bool() { return next_uint() & 1u; }
template <typename T>
T next_enum() {
constexpr uint32_t min = static_cast<uint32_t>(T::kMinValue);
constexpr uint32_t max = static_cast<uint32_t>(T::kMaxValue);
static_assert(min <= max, "min cannot be greater than max");
return static_cast<T>(min + next_uint(max - min));
}
private:
std::default_random_engine generator_;
std::uniform_int_distribution<uint32_t> distribution_;
};
apps::FileHandlers CreateRandomFileHandlers(uint32_t suffix) {
apps::FileHandlers file_handlers;
for (unsigned int i = 0; i < 5; ++i) {
std::string suffix_str =
base::NumberToString(suffix) + base::NumberToString(i);
apps::FileHandler::AcceptEntry accept_entry1;
accept_entry1.mime_type = "application/" + suffix_str + "+foo";
accept_entry1.file_extensions.insert("." + suffix_str + "a");
accept_entry1.file_extensions.insert("." + suffix_str + "b");
apps::FileHandler::AcceptEntry accept_entry2;
accept_entry2.mime_type = "application/" + suffix_str + "+bar";
accept_entry2.file_extensions.insert("." + suffix_str + "a");
accept_entry2.file_extensions.insert("." + suffix_str + "b");
apps::FileHandler file_handler;
file_handler.action = GURL("https://example.com/open-" + suffix_str);
file_handler.accept.push_back(std::move(accept_entry1));
file_handler.accept.push_back(std::move(accept_entry2));
file_handler.downloaded_icons.emplace_back(
GURL("https://example.com/image.png"), 16);
file_handler.downloaded_icons.emplace_back(
GURL("https://example.com/image2.png"), 48);
file_handler.display_name = base::ASCIIToUTF16(suffix_str) + u" file";
file_handlers.push_back(std::move(file_handler));
}
return file_handlers;
}
apps::ShareTarget CreateRandomShareTarget(uint32_t suffix) {
apps::ShareTarget share_target;
share_target.action =
GURL("https://example.com/path/target/" + base::NumberToString(suffix));
share_target.method = (suffix % 2 == 0) ? apps::ShareTarget::Method::kPost
: apps::ShareTarget::Method::kGet;
share_target.enctype = (suffix / 2 % 2 == 0)
? apps::ShareTarget::Enctype::kMultipartFormData
: apps::ShareTarget::Enctype::kFormUrlEncoded;
if (suffix % 3 != 0)
share_target.params.title = "title" + base::NumberToString(suffix);
if (suffix % 3 != 1)
share_target.params.text = "text" + base::NumberToString(suffix);
if (suffix % 3 != 2)
share_target.params.url = "url" + base::NumberToString(suffix);
for (uint32_t index = 0; index < suffix % 5; ++index) {
apps::ShareTarget::Files files;
files.name = "files" + base::NumberToString(index);
files.accept.push_back(".extension" + base::NumberToString(index));
files.accept.push_back("type/subtype" + base::NumberToString(index));
share_target.params.files.push_back(files);
}
return share_target;
}
blink::ParsedPermissionsPolicy CreateRandomPermissionsPolicy(
RandomHelper& random) {
const int num_permissions_policy_declarations =
random.next_uint(features.size());
std::vector<std::string> available_features = features;
const auto suffix = random.next_uint();
std::default_random_engine rng;
std::shuffle(available_features.begin(), available_features.end(), rng);
blink::ParsedPermissionsPolicy permissions_policy(
num_permissions_policy_declarations);
const auto& feature_name_map = blink::GetPermissionsPolicyNameToFeatureMap();
for (int i = 0; i < num_permissions_policy_declarations; ++i) {
permissions_policy[i].feature = feature_name_map.begin()->second;
for (unsigned int j = 0; j < 5; ++j) {
std::string suffix_str =
base::NumberToString(suffix) + base::NumberToString(j);
const auto origin =
url::Origin::Create(GURL("https://app-" + suffix_str + ".com/"));
permissions_policy[i].allowed_origins.emplace_back(
origin,
/*has_subdomain_wildcard=*/false);
}
}
return permissions_policy;
}
std::vector<apps::ProtocolHandlerInfo> CreateRandomProtocolHandlers(
uint32_t suffix) {
std::vector<apps::ProtocolHandlerInfo> protocol_handlers;
for (unsigned int i = 0; i < 5; ++i) {
std::string suffix_str =
base::NumberToString(suffix) + base::NumberToString(i);
apps::ProtocolHandlerInfo protocol_handler;
protocol_handler.protocol = "web+test" + suffix_str;
protocol_handler.url = GURL("https://example.com/").Resolve(suffix_str);
protocol_handlers.push_back(std::move(protocol_handler));
}
return protocol_handlers;
}
std::vector<apps::UrlHandlerInfo> CreateRandomUrlHandlers(uint32_t suffix) {
std::vector<apps::UrlHandlerInfo> url_handlers;
for (unsigned int i = 0; i < 3; ++i) {
std::string suffix_str =
base::NumberToString(suffix) + base::NumberToString(i);
apps::UrlHandlerInfo url_handler;
url_handler.origin =
url::Origin::Create(GURL("https://app-" + suffix_str + ".com/"));
url_handler.has_origin_wildcard = true;
url_handlers.push_back(std::move(url_handler));
}
return url_handlers;
}
ScopeExtensions CreateRandomScopeExtensions(uint32_t suffix,
RandomHelper& random) {
ScopeExtensions scope_extensions;
for (unsigned int i = 0; i < 3; ++i) {
std::string suffix_str =
base::NumberToString(suffix) + base::NumberToString(i);
ScopeExtensionInfo scope_extension;
scope_extension.origin =
url::Origin::Create(GURL("https://app-" + suffix_str + ".com/"));
scope_extension.has_origin_wildcard = random.next_bool();
scope_extensions.insert(std::move(scope_extension));
}
return scope_extensions;
}
std::vector<WebAppShortcutsMenuItemInfo> CreateRandomShortcutsMenuItemInfos(
const GURL& scope,
int num,
RandomHelper& random) {
const uint32_t suffix = random.next_uint();
std::vector<WebAppShortcutsMenuItemInfo> shortcuts_menu_item_infos;
for (int i = num - 1; i >= 0; --i) {
std::string suffix_str =
base::NumberToString(suffix) + base::NumberToString(i);
WebAppShortcutsMenuItemInfo shortcut_info;
shortcut_info.url = scope.Resolve("shortcut" + suffix_str);
shortcut_info.name = base::UTF8ToUTF16("shortcut" + suffix_str);
std::vector<WebAppShortcutsMenuItemInfo::Icon> shortcut_icons_any;
std::vector<WebAppShortcutsMenuItemInfo::Icon> shortcut_icons_maskable;
std::vector<WebAppShortcutsMenuItemInfo::Icon> shortcut_icons_monochrome;
for (int j = random.next_uint(4) + 1; j >= 0; --j) {
std::string icon_suffix_str = suffix_str + base::NumberToString(j);
WebAppShortcutsMenuItemInfo::Icon shortcut_icon;
shortcut_icon.url = scope.Resolve("/shortcuts/icon" + icon_suffix_str);
// Within each shortcut_icons_*, square_size_px must be unique.
shortcut_icon.square_size_px = (j * 10) + random.next_uint(10);
int icon_purpose = random.next_uint(3);
switch (icon_purpose) {
case 0:
shortcut_icons_any.push_back(std::move(shortcut_icon));
break;
case 1:
shortcut_icons_maskable.push_back(std::move(shortcut_icon));
break;
case 2:
shortcut_icons_monochrome.push_back(std::move(shortcut_icon));
break;
}
}
shortcut_info.SetShortcutIconInfosForPurpose(IconPurpose::ANY,
std::move(shortcut_icons_any));
shortcut_info.SetShortcutIconInfosForPurpose(
IconPurpose::MASKABLE, std::move(shortcut_icons_maskable));
shortcut_info.SetShortcutIconInfosForPurpose(
IconPurpose::MONOCHROME, std::move(shortcut_icons_monochrome));
shortcuts_menu_item_infos.push_back(std::move(shortcut_info));
}
return shortcuts_menu_item_infos;
}
std::vector<IconSizes> CreateRandomDownloadedShortcutsMenuIconsSizes(
int num,
RandomHelper& random) {
std::vector<IconSizes> results;
for (int i = 0; i < num; ++i) {
IconSizes result;
std::vector<SquareSizePx> shortcuts_menu_icon_sizes_any;
std::vector<SquareSizePx> shortcuts_menu_icon_sizes_maskable;
std::vector<SquareSizePx> shortcuts_menu_icon_sizes_monochrome;
for (int j = 0; j < i; ++j) {
shortcuts_menu_icon_sizes_any.push_back(random.next_uint(256) + 1);
shortcuts_menu_icon_sizes_maskable.push_back(random.next_uint(256) + 1);
shortcuts_menu_icon_sizes_monochrome.push_back(random.next_uint(256) + 1);
}
result.SetSizesForPurpose(IconPurpose::ANY,
std::move(shortcuts_menu_icon_sizes_any));
result.SetSizesForPurpose(IconPurpose::MASKABLE,
std::move(shortcuts_menu_icon_sizes_maskable));
result.SetSizesForPurpose(IconPurpose::MONOCHROME,
std::move(shortcuts_menu_icon_sizes_monochrome));
results.push_back(std::move(result));
}
return results;
}
std::vector<blink::Manifest::ImageResource> CreateRandomHomeTabIcons(
RandomHelper& random) {
std::vector<blink::Manifest::ImageResource> icons;
for (int i = random.next_uint(4) + 1; i >= 0; --i) {
blink::Manifest::ImageResource icon;
int mime_type = random.next_uint(3);
switch (mime_type) {
case 0:
icon.src = GURL("https://example.com/image" + base::NumberToString(i) +
".png");
icon.type = base::UTF8ToUTF16(std::string("image/png"));
break;
case 1:
icon.src = GURL("https://example.com/image" + base::NumberToString(i) +
".svg");
icon.type = base::UTF8ToUTF16(std::string("image/svg+xml"));
break;
case 2:
icon.src = GURL("https://example.com/image" + base::NumberToString(i) +
".webp");
icon.type = base::UTF8ToUTF16(std::string("image/webp"));
break;
}
// Icon sizes can be non square
std::vector<gfx::Size> sizes;
for (int j = random.next_uint(3) + 1; j > 0; --j) {
sizes.emplace_back(j * random.next_uint(200), j * random.next_uint(200));
}
icon.sizes = std::move(sizes);
std::vector<blink::mojom::ManifestImageResource_Purpose> purposes = {
blink::mojom::ManifestImageResource_Purpose::ANY,
blink::mojom::ManifestImageResource_Purpose::MASKABLE,
blink::mojom::ManifestImageResource_Purpose::MONOCHROME};
std::vector<blink::mojom::ManifestImageResource_Purpose> purpose;
for (int j = random.next_uint(purposes.size()); j >= 0; --j) {
unsigned index = random.next_uint(purposes.size());
purpose.push_back(purposes[index]);
purposes.erase(purposes.begin() + index);
}
icon.purpose = std::move(purpose);
icons.push_back(std::move(icon));
}
return icons;
}
proto::WebAppOsIntegrationState GenerateRandomWebAppOsIntegrationState(
RandomHelper& random,
WebApp& app) {
proto::WebAppOsIntegrationState state;
// Randomly fill shortcuts data.
auto* shortcuts = state.mutable_shortcut();
shortcuts->set_title(app.untranslated_name());
shortcuts->set_description(app.untranslated_description());
auto* first_shortcut = shortcuts->add_icon_data_any();
first_shortcut->set_icon_size(32);
first_shortcut->set_timestamp(syncer::TimeToProtoTime(
base::Time::UnixEpoch() + base::Milliseconds(random.next_uint())));
// Randomly fill protocols_handled.
auto* protocols_handled = state.mutable_protocols_handled();
int num_protocols = random.next_uint(/*bound=*/3);
for (int i = 0; i < num_protocols; i++) {
auto* protocol = protocols_handled->add_protocols();
protocol->set_protocol(base::StrCat({"web+test", base::NumberToString(i)}));
protocol->set_url(
base::StrCat({app.start_url().spec(), base::NumberToString(i)}));
}
// Randomly fill run_on_os_login.
const proto::RunOnOsLoginMode run_on_os_login_modes[3] = {
proto::RunOnOsLoginMode::NOT_RUN, proto::RunOnOsLoginMode::WINDOWED,
proto::RunOnOsLoginMode::MINIMIZED};
state.mutable_run_on_os_login()->set_run_on_os_login_mode(
run_on_os_login_modes[random.next_uint(/*bound=*/3)]);
// Randomly fill uninstallation registration logic.
state.mutable_uninstall_registration()->set_registered_with_os(
random.next_bool());
state.mutable_uninstall_registration()->set_display_name(
app.untranslated_name());
// Randomly fill shortcuts menu information.
auto* shortcut_menus = state.mutable_shortcut_menus();
int count_shortcut_menu_items = random.next_uint(/*bound=*/2);
for (int i = 0; i < count_shortcut_menu_items; i++) {
auto* menu_info = shortcut_menus->add_shortcut_menu_info();
menu_info->set_shortcut_name(
base::StrCat({"shortcut_name", base::NumberToString(i)}));
menu_info->set_shortcut_launch_url(
base::StrCat({app.scope().spec(), base::NumberToString(i)}));
auto* data_any = menu_info->add_icon_data_any();
data_any->set_icon_size(16 * random.next_uint(/*bound=*/4));
data_any->set_timestamp(syncer::TimeToProtoTime(
base::Time::UnixEpoch() + base::Milliseconds(random.next_uint())));
auto* data_maskable = menu_info->add_icon_data_maskable();
data_maskable->set_icon_size(16 * random.next_uint(/*bound=*/4));
data_maskable->set_timestamp(syncer::TimeToProtoTime(
base::Time::UnixEpoch() + base::Milliseconds(random.next_uint())));
auto* data_monochrome = menu_info->add_icon_data_monochrome();
data_monochrome->set_icon_size(16 * random.next_uint(/*bound=*/4));
data_monochrome->set_timestamp(syncer::TimeToProtoTime(
base::Time::UnixEpoch() + base::Milliseconds(random.next_uint())));
}
// Randomly fill file handling information.
auto* file_handling = state.mutable_file_handling();
int count_file_handlers = random.next_uint(/*bound=*/2);
for (int i = 0; i < count_file_handlers; i++) {
auto* file_handlers = file_handling->add_file_handlers();
int count_accepts = random.next_uint(/*bound=*/2);
file_handlers->set_action(
base::StrCat({"https://file.open/", base::NumberToString(i)}));
file_handlers->set_display_name(
base::StrCat({"file_type", base::NumberToString(i)}));
for (int j = 0; j < count_accepts; j++) {
auto* accept = file_handlers->add_accept();
accept->set_mimetype(
base::StrCat({"application/type", base::NumberToString(i)}));
accept->add_file_extensions(
base::StrCat({"foo", base::NumberToString(i)}));
accept->add_file_extensions(
base::StrCat({"bar", base::NumberToString(i)}));
}
}
return state;
}
} // namespace
std::string GetExternalPrefMigrationTestName(
const ::testing::TestParamInfo<ExternalPrefMigrationTestCases>& info) {
switch (info.param) {
case ExternalPrefMigrationTestCases::kDisableMigrationReadPref:
return "DisableMigration_ReadFromPrefs";
case ExternalPrefMigrationTestCases::kDisableMigrationReadDB:
return "DisableMigration_ReadFromDB";
case ExternalPrefMigrationTestCases::kEnableMigrationReadPref:
return "EnableMigration_ReadFromPrefs";
case ExternalPrefMigrationTestCases::kEnableMigrationReadDB:
return "EnableMigration_ReadFromDB";
}
}
std::string GetOsIntegrationSubManagersTestName(
const ::testing::TestParamInfo<OsIntegrationSubManagersState>& info) {
switch (info.param) {
case OsIntegrationSubManagersState::kSaveStateToDB:
return "OSIntegrationSubManagers_SaveStateToDB";
case OsIntegrationSubManagersState::kSaveStateAndExecute:
return "OSIntegrationSubManagers_SaveStateAndExecute";
case OsIntegrationSubManagersState::kDisabled:
return "OSIntegrationSubManagers_Disabled";
}
}
std::unique_ptr<WebApp> CreateWebApp(const GURL& start_url,
WebAppManagement::Type source_type) {
const AppId app_id = GenerateAppId(/*manifest_id=*/absl::nullopt, start_url);
auto web_app = std::make_unique<WebApp>(app_id);
web_app->SetStartUrl(start_url);
web_app->AddSource(source_type);
web_app->SetUserDisplayMode(mojom::UserDisplayMode::kStandalone);
web_app->SetName("Name");
web_app->SetIsLocallyInstalled(true);
return web_app;
}
std::unique_ptr<WebApp> CreateRandomWebApp(const GURL& base_url,
const uint32_t seed,
bool allow_system_source) {
RandomHelper random(seed);
const std::string seed_str = base::NumberToString(seed);
absl::optional<std::string> manifest_id;
if (random.next_bool())
manifest_id = "manifest_id_" + seed_str;
const GURL scope = base_url.Resolve("scope" + seed_str + "/");
const GURL start_url = scope.Resolve("start" + seed_str);
const AppId app_id = GenerateAppId(manifest_id, start_url);
const std::string name = "Name" + seed_str;
const std::string description = "Description" + seed_str;
const absl::optional<SkColor> theme_color = random.next_uint();
absl::optional<SkColor> dark_mode_theme_color;
const absl::optional<SkColor> background_color = random.next_uint();
absl::optional<SkColor> dark_mode_background_color;
const absl::optional<SkColor> synced_theme_color = random.next_uint();
auto app = std::make_unique<WebApp>(app_id);
std::vector<WebAppManagement::Type> management_types;
// Generate all possible permutations of field values in a random way:
if (allow_system_source && random.next_bool()) {
app->AddSource(WebAppManagement::kSystem);
management_types.push_back(WebAppManagement::kSystem);
}
if (random.next_bool()) {
app->AddSource(WebAppManagement::kPolicy);
management_types.push_back(WebAppManagement::kPolicy);
}
if (random.next_bool()) {
app->AddSource(WebAppManagement::kWebAppStore);
management_types.push_back(WebAppManagement::kWebAppStore);
}
if (random.next_bool()) {
app->AddSource(WebAppManagement::kSync);
management_types.push_back(WebAppManagement::kSync);
}
if (random.next_bool()) {
app->AddSource(WebAppManagement::kDefault);
management_types.push_back(WebAppManagement::kDefault);
}
if (random.next_bool()) {
app->AddSource(WebAppManagement::kSubApp);
management_types.push_back(WebAppManagement::kSubApp);
}
if (random.next_bool()) {
app->AddSource(WebAppManagement::kKiosk);
management_types.push_back(WebAppManagement::kKiosk);
}
if (random.next_bool()) {
app->AddSource(WebAppManagement::kCommandLine);
management_types.push_back(WebAppManagement::kCommandLine);
}
if (random.next_bool()) {
app->AddSource(WebAppManagement::kOem);
management_types.push_back(WebAppManagement::kOem);
}
if (random.next_bool()) {
app->AddSource(WebAppManagement::kOneDriveIntegration);
management_types.push_back(WebAppManagement::kOneDriveIntegration);
}
// Must always be at least one source.
if (!app->HasAnySources()) {
app->AddSource(WebAppManagement::kSync);
management_types.push_back(WebAppManagement::kSync);
}
if (random.next_bool()) {
dark_mode_theme_color = SkColorSetA(random.next_uint(), SK_AlphaOPAQUE);
}
if (random.next_bool()) {
dark_mode_background_color =
SkColorSetA(random.next_uint(), SK_AlphaOPAQUE);
}
app->SetName(name);
app->SetDescription(description);
app->SetManifestId(manifest_id);
app->SetStartUrl(GURL(start_url));
app->SetScope(GURL(scope));
app->SetThemeColor(theme_color);
app->SetDarkModeThemeColor(dark_mode_theme_color);
app->SetBackgroundColor(background_color);
app->SetDarkModeBackgroundColor(dark_mode_background_color);
app->SetIsLocallyInstalled(random.next_bool());
app->SetIsFromSyncAndPendingInstallation(random.next_bool());
const mojom::UserDisplayMode user_display_modes[3] = {
mojom::UserDisplayMode::kBrowser, mojom::UserDisplayMode::kStandalone,
mojom::UserDisplayMode::kTabbed};
app->SetUserDisplayMode(user_display_modes[random.next_uint(3)]);
const base::Time last_badging_time =
base::Time::UnixEpoch() + base::Milliseconds(random.next_uint());
app->SetLastBadgingTime(last_badging_time);
const base::Time last_launch_time =
base::Time::UnixEpoch() + base::Milliseconds(random.next_uint());
app->SetLastLaunchTime(last_launch_time);
const base::Time install_time =
base::Time::UnixEpoch() + base::Milliseconds(random.next_uint());
app->SetInstallTime(install_time);
const DisplayMode display_modes[4] = {
DisplayMode::kBrowser, DisplayMode::kMinimalUi, DisplayMode::kStandalone,
DisplayMode::kFullscreen};
app->SetDisplayMode(display_modes[random.next_uint(4)]);
// Add only unique display modes.
std::set<DisplayMode> display_mode_override;
int num_display_mode_override_tries = random.next_uint(5);
for (int i = 0; i < num_display_mode_override_tries; i++)
display_mode_override.insert(display_modes[random.next_uint(4)]);
app->SetDisplayModeOverride(std::vector<DisplayMode>(
display_mode_override.begin(), display_mode_override.end()));
if (random.next_bool())
app->SetLaunchQueryParams(base::NumberToString(random.next_uint()));
app->SetRunOnOsLoginMode(random.next_enum<RunOnOsLoginMode>());
app->SetRunOnOsLoginOsIntegrationState(RunOnOsLoginMode::kNotRun);
const SquareSizePx size = 256;
const int num_icons = random.next_uint(10);
std::vector<apps::IconInfo> manifest_icons(num_icons);
for (int i = 0; i < num_icons; i++) {
apps::IconInfo icon;
icon.url =
base_url.Resolve("/icon" + base::NumberToString(random.next_uint()));
if (random.next_bool())
icon.square_size_px = size;
int purpose = random.next_uint(4);
if (purpose == 0)
icon.purpose = apps::IconInfo::Purpose::kAny;
if (purpose == 1)
icon.purpose = apps::IconInfo::Purpose::kMaskable;
if (purpose == 2)
icon.purpose = apps::IconInfo::Purpose::kMonochrome;
// if (purpose == 3), leave purpose unset. Should default to ANY.
manifest_icons[i] = icon;
}
app->SetManifestIcons(manifest_icons);
if (random.next_bool())
app->SetDownloadedIconSizes(IconPurpose::ANY, {size});
if (random.next_bool())
app->SetDownloadedIconSizes(IconPurpose::MASKABLE, {size});
if (random.next_bool())
app->SetDownloadedIconSizes(IconPurpose::MONOCHROME, {size});
app->SetIsGeneratedIcon(random.next_bool());
app->SetFileHandlers(CreateRandomFileHandlers(random.next_uint()));
if (random.next_bool())
app->SetShareTarget(CreateRandomShareTarget(random.next_uint()));
app->SetProtocolHandlers(CreateRandomProtocolHandlers(random.next_uint()));
app->SetUrlHandlers(CreateRandomUrlHandlers(random.next_uint()));
app->SetScopeExtensions(
CreateRandomScopeExtensions(random.next_uint(), random));
ScopeExtensions validated_scope_extensions;
base::ranges::copy_if(app->scope_extensions(),
std::inserter(validated_scope_extensions,
validated_scope_extensions.begin()),
[&random](const ScopeExtensionInfo& extension) {
return random.next_bool();
});
app->SetValidatedScopeExtensions(validated_scope_extensions);
if (random.next_bool()) {
app->SetLockScreenStartUrl(scope.Resolve(
"lock_screen_start_url" + base::NumberToString(random.next_uint())));
}
if (random.next_bool()) {
app->SetNoteTakingNewNoteUrl(
scope.Resolve("new_note" + base::NumberToString(random.next_uint())));
}
app->SetCaptureLinks(random.next_enum<blink::mojom::CaptureLinks>());
const int num_additional_search_terms = random.next_uint(8);
std::vector<std::string> additional_search_terms(num_additional_search_terms);
for (int i = 0; i < num_additional_search_terms; ++i) {
additional_search_terms[i] =
"Foo_" + seed_str + "_" + base::NumberToString(i);
}
app->SetAdditionalSearchTerms(std::move(additional_search_terms));
int num_shortcut_menus = static_cast<int>(random.next_uint(4)) + 1;
app->SetShortcutsMenuItemInfos(
CreateRandomShortcutsMenuItemInfos(scope, num_shortcut_menus, random));
app->SetDownloadedShortcutsMenuIconsSizes(
CreateRandomDownloadedShortcutsMenuIconsSizes(num_shortcut_menus,
random));
CHECK_EQ(app->shortcuts_menu_item_infos().size(),
app->downloaded_shortcuts_menu_icons_sizes().size());
app->SetManifestUrl(base_url.Resolve("/manifest" + seed_str + ".json"));
const int num_allowed_launch_protocols = random.next_uint(8);
std::vector<std::string> allowed_launch_protocols(
num_allowed_launch_protocols);
for (int i = 0; i < num_allowed_launch_protocols; ++i) {
allowed_launch_protocols[i] =
"web+test_" + seed_str + "_" + base::NumberToString(i);
}
app->SetAllowedLaunchProtocols(std::move(allowed_launch_protocols));
const int num_disallowed_launch_protocols = random.next_uint(8);
std::vector<std::string> disallowed_launch_protocols(
num_disallowed_launch_protocols);
for (int i = 0; i < num_disallowed_launch_protocols; ++i) {
disallowed_launch_protocols[i] =
"web+disallowed_" + seed_str + "_" + base::NumberToString(i);
}
app->SetDisallowedLaunchProtocols(std::move(disallowed_launch_protocols));
app->SetWindowControlsOverlayEnabled(false);
WebApp::SyncFallbackData sync_fallback_data;
sync_fallback_data.name = "Sync" + name;
sync_fallback_data.theme_color = synced_theme_color;
sync_fallback_data.scope = app->scope();
sync_fallback_data.icon_infos = app->manifest_icons();
app->SetSyncFallbackData(std::move(sync_fallback_data));
if (random.next_bool()) {
app->SetLaunchHandler(
LaunchHandler{random.next_enum<LaunchHandler::ClientMode>()});
}
const base::Time manifest_update_time =
base::Time::UnixEpoch() + base::Milliseconds(random.next_uint());
app->SetManifestUpdateTime(manifest_update_time);
if (random.next_bool())
app->SetParentAppId(base::NumberToString(random.next_uint()));
if (random.next_bool())
app->SetPermissionsPolicy(CreateRandomPermissionsPolicy(random));
uint32_t install_source =
random.next_uint(static_cast<int>(webapps::WebappInstallSource::COUNT));
app->SetLatestInstallSource(
static_cast<webapps::WebappInstallSource>(install_source));
if (IsChromeOsDataMandatory()) {
// Use a separate random generator for CrOS so the result is deterministic
// across cros and non-cros builds.
RandomHelper cros_random(seed);
auto chromeos_data = absl::make_optional<WebAppChromeOsData>();
chromeos_data->show_in_launcher = cros_random.next_bool();
chromeos_data->show_in_search = cros_random.next_bool();
chromeos_data->show_in_management = cros_random.next_bool();
chromeos_data->is_disabled = cros_random.next_bool();
chromeos_data->oem_installed = cros_random.next_bool();
// Comply with DCHECK that system apps cannot be OEM installed.
if (app->IsSystemApp())
chromeos_data->oem_installed = false;
app->SetWebAppChromeOsData(std::move(chromeos_data));
}
WebApp::ExternalConfigMap management_to_external_config;
for (WebAppManagement::Type type : management_types) {
if (type == WebAppManagement::kSync)
continue;
base::flat_set<GURL> install_urls;
base::flat_set<std::string> additional_policy_ids;
WebApp::ExternalManagementConfig config;
if (random.next_bool()) {
install_urls.emplace(base_url.Resolve("installer1_" + seed_str + "/"));
}
if (random.next_bool()) {
install_urls.emplace(base_url.Resolve("installer2_" + seed_str + "/"));
}
if (random.next_bool()) {
additional_policy_ids.emplace("policy_id_1_" + seed_str);
}
if (random.next_bool()) {
additional_policy_ids.emplace("policy_id_2_" + seed_str);
}
config.is_placeholder = random.next_bool();
config.install_urls = std::move(install_urls);
config.additional_policy_ids = std::move(additional_policy_ids);
management_to_external_config.insert_or_assign(type, std::move(config));
}
app->SetWebAppManagementExternalConfigMap(management_to_external_config);
app->SetAppSizeInBytes(random.next_uint());
app->SetDataSizeInBytes(random.next_uint());
if (random.next_bool()) {
blink::Manifest::TabStrip tab_strip;
if (random.next_bool()) {
blink::Manifest::HomeTabParams home_tab_params;
if (random.next_bool()) {
home_tab_params.icons = CreateRandomHomeTabIcons(random);
}
tab_strip.home_tab = std::move(home_tab_params);
} else {
tab_strip.home_tab =
random.next_enum<blink::mojom::TabStripMemberVisibility>();
}
if (random.next_bool()) {
blink::Manifest::NewTabButtonParams new_tab_button_params;
if (random.next_bool()) {
new_tab_button_params.url = scope.Resolve(
"new_tab_button_url" + base::NumberToString(random.next_uint()));
}
tab_strip.new_tab_button = new_tab_button_params;
} else {
tab_strip.new_tab_button =
random.next_enum<blink::mojom::TabStripMemberVisibility>();
}
app->SetTabStrip(std::move(tab_strip));
}
app->SetAlwaysShowToolbarInFullscreen(random.next_bool());
app->SetCurrentOsIntegrationStates(
GenerateRandomWebAppOsIntegrationState(random, *app));
if (random.next_bool()) {
constexpr size_t kNumLocationTypes =
absl::variant_size<IsolatedWebAppLocation>::value;
auto path = base::FilePath::FromUTF8Unsafe(seed_str);
IsolatedWebAppLocation location_types[] = {
InstalledBundle{.path = path},
DevModeBundle{.path = path},
DevModeProxy{.proxy_url = url::Origin::Create(GURL(
base::StrCat({"https://proxy-", seed_str, ".com/"})))},
};
static_assert(std::size(location_types) == kNumLocationTypes);
IsolatedWebAppLocation location(
location_types[random.next_uint(kNumLocationTypes)]);
app->SetIsolationData(WebApp::IsolationData(location));
}
return app;
}
void TestAcceptDialogCallback(
content::WebContents* initiator_web_contents,
std::unique_ptr<WebAppInstallInfo> web_app_info,
WebAppInstallationAcceptanceCallback acceptance_callback) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(acceptance_callback), true /*accept*/,
std::move(web_app_info)));
}
void TestDeclineDialogCallback(
content::WebContents* initiator_web_contents,
std::unique_ptr<WebAppInstallInfo> web_app_info,
WebAppInstallationAcceptanceCallback acceptance_callback) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(acceptance_callback),
false /*accept*/, std::move(web_app_info)));
}
AppId InstallPwaForCurrentUrl(Browser* browser) {
// Depending on the installability criteria, different dialogs can be used.
chrome::SetAutoAcceptWebAppDialogForTesting(true, true);
chrome::SetAutoAcceptPWAInstallConfirmationForTesting(true);
WebAppTestInstallObserver observer(browser->profile());
observer.BeginListening();
CHECK(chrome::ExecuteCommand(browser, IDC_INSTALL_PWA));
AppId app_id = observer.Wait();
chrome::SetAutoAcceptPWAInstallConfirmationForTesting(false);
chrome::SetAutoAcceptWebAppDialogForTesting(false, false);
return app_id;
}
void CheckServiceWorkerStatus(const GURL& url,
content::StoragePartition* storage_partition,
content::ServiceWorkerCapability status) {
base::RunLoop run_loop;
content::ServiceWorkerContext* service_worker_context =
storage_partition->GetServiceWorkerContext();
service_worker_context->CheckHasServiceWorker(
url, blink::StorageKey::CreateFirstParty(url::Origin::Create(url)),
base::BindLambdaForTesting(
[&run_loop, status](content::ServiceWorkerCapability capability) {
CHECK_EQ(status, capability);
run_loop.Quit();
}));
run_loop.Run();
}
void SetWebAppSettingsListPref(Profile* profile, const base::StringPiece pref) {
auto result = base::JSONReader::ReadAndReturnValueWithError(
pref, base::JSONParserOptions::JSON_ALLOW_TRAILING_COMMAS);
DCHECK(result.has_value()) << result.error().message;
DCHECK(result->is_list());
profile->GetPrefs()->Set(prefs::kWebAppSettings, std::move(*result));
}
void AddInstallUrlData(PrefService* pref_service,
WebAppSyncBridge* sync_bridge,
const AppId& app_id,
const GURL& url,
const ExternalInstallSource& source) {
ScopedRegistryUpdate update(sync_bridge);
WebApp* app_to_update = update->UpdateApp(app_id);
DCHECK(app_to_update);
// Adding external app data (source and URL) to web_app DB.
app_to_update->AddInstallURLToManagementExternalConfigMap(
ConvertExternalInstallSourceToSource(source), url);
// Add to legacy external pref storage.
// TODO(crbug.com/1339965): Clean up after external pref migration is
// complete.
ExternallyInstalledWebAppPrefs(pref_service).Insert(url, app_id, source);
}
void AddInstallUrlAndPlaceholderData(PrefService* pref_service,
WebAppSyncBridge* sync_bridge,
const AppId& app_id,
const GURL& url,
const ExternalInstallSource& source,
bool is_placeholder) {
ScopedRegistryUpdate update(sync_bridge);
ExternallyInstalledWebAppPrefs prefs(pref_service);
WebApp* app_to_update = update->UpdateApp(app_id);
DCHECK(app_to_update);
// Adding install_url, source and placeholder information to the web_app DB.
app_to_update->AddExternalSourceInformation(
ConvertExternalInstallSourceToSource(source), url, is_placeholder);
// Add to legacy external pref storage.
// TODO(crbug.com/1339965): Clean up after external pref migration is
// complete.
prefs.Insert(url, app_id, source);
prefs.SetIsPlaceholder(url, is_placeholder);
}
#if BUILDFLAG(IS_CHROMEOS_LACROS)
ScopedSkipMainProfileCheck::ScopedSkipMainProfileCheck() {
EXPECT_FALSE(IsMainProfileCheckSkippedForTesting());
SetSkipMainProfileCheckForTesting(/*skip_check=*/true);
}
ScopedSkipMainProfileCheck::~ScopedSkipMainProfileCheck() {
SetSkipMainProfileCheckForTesting(/*skip_check=*/false);
}
#endif
} // namespace test
} // namespace web_app