blob: db67c4fb6ec82a1132fc3d09f71418dff6b5344a [file] [log] [blame]
// Copyright 2020 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/test/web_app_test_utils.h"
#include <random>
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/web_applications/web_app_helpers.h"
#include "chrome/browser/web_applications/web_app_utils.h"
#include "chrome/browser/web_applications/web_application_info.h"
#include "components/services/app_service/public/cpp/url_handler_info.h"
#include "third_party/blink/public/common/manifest/manifest.h"
#include "url/gurl.h"
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;
}
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;
}
std::vector<WebApplicationShortcutsMenuItemInfo>
CreateRandomShortcutsMenuItemInfos(const GURL& scope, RandomHelper& random) {
const uint32_t suffix = random.next_uint();
std::vector<WebApplicationShortcutsMenuItemInfo> shortcuts_menu_item_infos;
for (int i = random.next_uint(4) + 1; i >= 0; --i) {
std::string suffix_str =
base::NumberToString(suffix) + base::NumberToString(i);
WebApplicationShortcutsMenuItemInfo shortcut_info;
shortcut_info.url = scope.Resolve("shortcut" + suffix_str);
shortcut_info.name = base::UTF8ToUTF16("shortcut" + suffix_str);
std::vector<WebApplicationShortcutsMenuItemInfo::Icon> shortcut_icons_any;
std::vector<WebApplicationShortcutsMenuItemInfo::Icon>
shortcut_icons_maskable;
std::vector<WebApplicationShortcutsMenuItemInfo::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);
WebApplicationShortcutsMenuItemInfo::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.emplace_back(std::move(shortcut_info));
}
return shortcuts_menu_item_infos;
}
std::vector<IconSizes> CreateRandomDownloadedShortcutsMenuIconsSizes(
RandomHelper& random) {
std::vector<IconSizes> results;
for (unsigned int i = 0; i < 3; ++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 (unsigned int j = 0; j < i; ++j) {
shortcuts_menu_icon_sizes_any.emplace_back(random.next_uint(256) + 1);
shortcuts_menu_icon_sizes_maskable.emplace_back(random.next_uint(256) +
1);
shortcuts_menu_icon_sizes_monochrome.emplace_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.emplace_back(std::move(result));
}
return results;
}
} // namespace
std::unique_ptr<WebApp> CreateWebApp(const GURL& start_url,
Source::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(DisplayMode::kStandalone);
web_app->SetName("Name");
return web_app;
}
std::unique_ptr<WebApp> CreateRandomWebApp(const GURL& base_url,
const uint32_t seed) {
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();
const absl::optional<SkColor> background_color = random.next_uint();
const absl::optional<SkColor> synced_theme_color = random.next_uint();
auto app = std::make_unique<WebApp>(app_id);
// Generate all possible permutations of field values in a random way:
if (AreSystemWebAppsSupported() && random.next_bool())
app->AddSource(Source::kSystem);
if (random.next_bool())
app->AddSource(Source::kPolicy);
if (random.next_bool())
app->AddSource(Source::kWebAppStore);
if (random.next_bool())
app->AddSource(Source::kSync);
if (random.next_bool())
app->AddSource(Source::kDefault);
// Must always be at least one source.
if (!app->HasAnySources())
app->AddSource(Source::kSync);
app->SetName(name);
app->SetDescription(description);
app->SetManifestId(manifest_id);
app->SetStartUrl(GURL(start_url));
app->SetScope(GURL(scope));
app->SetThemeColor(theme_color);
app->SetBackgroundColor(background_color);
app->SetIsLocallyInstalled(random.next_bool());
app->SetIsFromSyncAndPendingInstallation(random.next_bool());
const DisplayMode user_display_modes[3] = {
DisplayMode::kBrowser, DisplayMode::kStandalone, DisplayMode::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>());
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()));
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));
app->SetShortcutsMenuItemInfos(
CreateRandomShortcutsMenuItemInfos(scope, random));
app->SetDownloadedShortcutsMenuIconsSizes(
CreateRandomDownloadedShortcutsMenuIconsSizes(random));
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->SetStorageIsolated(random.next_bool());
app->SetFileHandlerPermissionBlocked(false);
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()) {
LaunchHandler launch_handler;
launch_handler.route_to = random.next_enum<LaunchHandler::RouteTo>();
launch_handler.navigate_existing_client =
random.next_enum<LaunchHandler::NavigateExistingClient>();
app->SetLaunchHandler(launch_handler);
}
const base::Time manifest_update_time =
base::Time::UnixEpoch() + base::Milliseconds(random.next_uint());
app->SetManifestUpdateTime(manifest_update_time);
// `random` should not be used after the chromeos block if the result
// is expected to be deterministic across cros and non-cros builds.
if (IsChromeOsDataMandatory()) {
auto chromeos_data = absl::make_optional<WebAppChromeOsData>();
chromeos_data->show_in_launcher = random.next_bool();
chromeos_data->show_in_search = random.next_bool();
chromeos_data->show_in_management = random.next_bool();
chromeos_data->is_disabled = random.next_bool();
chromeos_data->oem_installed = random.next_bool();
app->SetWebAppChromeOsData(std::move(chromeos_data));
}
return app;
}
void TestAcceptDialogCallback(
content::WebContents* initiator_web_contents,
std::unique_ptr<WebApplicationInfo> web_app_info,
ForInstallableSite for_installable_site,
WebAppInstallationAcceptanceCallback acceptance_callback) {
base::ThreadTaskRunnerHandle::Get()->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<WebApplicationInfo> web_app_info,
ForInstallableSite for_installable_site,
WebAppInstallationAcceptanceCallback acceptance_callback) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(acceptance_callback),
false /*accept*/, std::move(web_app_info)));
}
} // namespace test
} // namespace web_app