| // 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 <stddef.h> |
| |
| #include <algorithm> |
| #include <array> |
| #include <cstdint> |
| #include <functional> |
| #include <iterator> |
| #include <limits> |
| #include <optional> |
| #include <ostream> |
| #include <random> |
| #include <set> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| #include <variant> |
| #include <vector> |
| |
| #include "base/base64.h" |
| #include "base/check.h" |
| #include "base/check_op.h" |
| #include "base/containers/flat_set.h" |
| #include "base/containers/flat_tree.h" |
| #include "base/containers/span.h" |
| #include "base/files/file_path.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/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/sequenced_task_runner.h" |
| #include "base/test/bind.h" |
| #include "base/test/test_future.h" |
| #include "base/time/time.h" |
| #include "base/values.h" |
| #include "base/version.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/web_applications/web_app_dialogs.h" |
| #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_integrity_block_data.h" |
| #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_storage_location.h" |
| #include "chrome/browser/web_applications/isolated_web_apps/isolation_data.h" |
| #include "chrome/browser/web_applications/mojom/user_display_mode.mojom.h" |
| #include "chrome/browser/web_applications/proto/web_app.pb.h" |
| #include "chrome/browser/web_applications/proto/web_app_isolation_data.pb.h" |
| #include "chrome/browser/web_applications/proto/web_app_os_integration_state.pb.h" |
| #include "chrome/browser/web_applications/scope_extension_info.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_command_scheduler.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_install_utils.h" |
| #include "chrome/browser/web_applications/web_app_management_type.h" |
| #include "chrome/browser/web_applications/web_app_proto_utils.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/chrome_features.h" |
| #include "chrome/common/pref_names.h" |
| #include "components/base32/base32.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/sync/base/time.h" |
| #include "components/sync/model/string_ordinal.h" |
| #include "components/sync/protocol/web_app_specifics.pb.h" |
| #include "components/webapps/browser/installable/installable_metrics.h" |
| #include "components/webapps/isolated_web_apps/update_channel.h" |
| #include "content/public/browser/service_worker_context.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "services/network/public/cpp/permissions_policy/origin_with_possible_wildcards.h" |
| #include "services/network/public/cpp/permissions_policy/permissions_policy_declaration.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/common/manifest/manifest.h" |
| #include "third_party/blink/public/common/permissions_policy/policy_helper_public.h" |
| #include "third_party/blink/public/common/safe_url_pattern.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/blink/public/mojom/manifest/display_mode.mojom-shared.h" |
| #include "third_party/blink/public/mojom/manifest/manifest.mojom-shared.h" |
| #include "third_party/blink/public/mojom/manifest/manifest_launch_handler.mojom-shared.h" |
| #include "third_party/liburlpattern/pattern.h" |
| #include "third_party/skia/include/core/SkColor.h" |
| #include "ui/gfx/geometry/size.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| |
| namespace { |
| |
| std::vector<std::string> test_features = { |
| "default_on_feature", "default_self_feature", "default_disabled_feature"}; |
| |
| constexpr std::string_view kEd25519PublicKeyBase64 = |
| "3QrKLntnXWmBkrbLLoobC0uh7u0ahMcKQSqsbX4q7MM="; |
| constexpr std::string_view kEd25519SignatureHex = |
| "94FBD9E3E0C0A4CD475067F1DC315E9EA30A5010350A9D3A4392CA9E0EBA854315372D7A6B" |
| "48B6C4DDF9A5FAAE4E159D3B80632413DA2850B54A4D5D98EAE906"; |
| |
| constexpr std::string_view kEcdsaP256PublicKeyBase64 = |
| "AxyCBzvQfu1yaF01392k1gu2qCtT1uA2+WfIEhlyJB5S"; |
| constexpr std::string_view kEcdsaP256SHA256SignatureHex = |
| "3044022007381524F538B04F99CCC62703F06C87F66EF41BDA18A22D8E57952AA23E53A6" |
| "022063C7F81D3A44798CB95823FA38FC23B15E0483744657FF49E1E83AB8C06B63C2"; |
| |
| } // namespace |
| |
| namespace web_app::test { |
| |
| namespace { |
| |
| class RandomHelper { |
| public: |
| explicit RandomHelper(const uint32_t seed, bool non_zero) |
| : // Seed of 0 and 1 generate the same sequence, so skip 0. |
| generator_(seed + 1), |
| distribution_(0u, UINT32_MAX), |
| non_zero_(non_zero) {} |
| |
| uint32_t next_uint() { |
| return std::max(distribution_(generator_), |
| static_cast<uint32_t>(non_zero_)); |
| } |
| |
| // Return an unsigned int between 0 (inclusive) and bound (exclusive). |
| uint32_t next_uint(uint32_t bound) { |
| if (bound <= 1) { |
| return 0; |
| } |
| uint32_t value = next_uint() % bound; |
| if (value == 0 && non_zero_) { |
| value = 1; |
| } |
| CHECK_LT(value, bound); |
| return value; |
| } |
| |
| bool next_bool() { return non_zero_ || next_uint() & 1u; } |
| |
| base::Time next_time() { |
| return base::Time::UnixEpoch() + base::Milliseconds(next_uint()); |
| } |
| |
| int64_t next_proto_time() { return syncer::TimeToProtoTime(next_time()); } |
| |
| // max is inclusive. |
| template <typename T, auto min = T::kMinValue, auto max = T::kMaxValue> |
| T next_enum() { |
| constexpr uint32_t min_u32 = static_cast<uint32_t>(min); |
| constexpr uint32_t max_u32_exclusive = static_cast<uint32_t>(max) + 1; |
| static_assert(min_u32 < max_u32_exclusive, |
| "min cannot be greater than max"); |
| return static_cast<T>(min_u32 + next_uint(max_u32_exclusive - min_u32)); |
| } |
| |
| private: |
| std::default_random_engine generator_; |
| std::uniform_int_distribution<uint32_t> distribution_; |
| bool non_zero_; |
| }; |
| |
| #define NEXT_PROTO_ENUM(random_helper, T, skip_zero) \ |
| static_cast<T>(static_cast<uint32_t>(skip_zero) + \ |
| random_helper.next_uint(T##_MAX - T##_MIN - \ |
| static_cast<uint32_t>(skip_zero))) |
| |
| 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; |
| } |
| |
| network::ParsedPermissionsPolicy CreateRandomPermissionsPolicy( |
| RandomHelper& random) { |
| const int num_permissions_policy_declarations = |
| random.next_uint(test_features.size()); |
| |
| std::vector<std::string> available_features = test_features; |
| |
| const auto suffix = random.next_uint(); |
| std::default_random_engine rng; |
| std::shuffle(available_features.begin(), available_features.end(), rng); |
| |
| network::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( |
| *network::OriginWithPossibleWildcards::FromOrigin(origin)); |
| } |
| } |
| 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; |
| } |
| |
| 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); |
| |
| if (random.next_bool()) { |
| auto scope_extension = ScopeExtensionInfo::CreateForOrigin( |
| url::Origin::Create(GURL("https://app-" + suffix_str + ".com/")), |
| /*has_origin_wildcard*/ random.next_bool()); |
| scope_extensions.insert(std::move(scope_extension)); |
| } else { |
| std::string random_scope = base::NumberToString(random.next_uint()); |
| auto scope_extension = ScopeExtensionInfo::CreateForScope( |
| GURL("https://app-" + suffix_str + ".com/" + random_scope), |
| /*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) { |
| int x = j * random.next_uint(200); |
| int y = j * random.next_uint(200); |
| sizes.emplace_back(x, y); |
| } |
| 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; |
| } |
| |
| std::vector<blink::SafeUrlPattern> CreateRandomScopePatterns( |
| RandomHelper& random) { |
| std::vector<blink::SafeUrlPattern> scope_patterns; |
| |
| for (int i = random.next_uint(4) + 1; i >= 0; --i) { |
| blink::SafeUrlPattern url_pattern; |
| |
| for (int j = random.next_uint(4) + 1; j >= 0; --j) { |
| liburlpattern::Part part; |
| |
| std::vector<liburlpattern::PartType> part_types = { |
| liburlpattern::PartType::kFixed, |
| liburlpattern::PartType::kFullWildcard, |
| liburlpattern::PartType::kSegmentWildcard}; |
| std::vector<liburlpattern::Modifier> modifiers = { |
| liburlpattern::Modifier::kZeroOrMore, |
| liburlpattern::Modifier::kOptional, |
| liburlpattern::Modifier::kOneOrMore, liburlpattern::Modifier::kNone}; |
| part.type = part_types[random.next_uint(part_types.size())]; |
| part.value = "value" + base::NumberToString(j); |
| if (part.type == liburlpattern::PartType::kFullWildcard || |
| part.type == liburlpattern::PartType::kSegmentWildcard) { |
| part.prefix = "prefix" + base::NumberToString(j); |
| part.name = "name" + base::NumberToString(j); |
| part.suffix = "suffix" + base::NumberToString(j); |
| } |
| |
| part.modifier = modifiers[random.next_uint(modifiers.size())]; |
| |
| url_pattern.pathname.push_back(std::move(part)); |
| } |
| |
| scope_patterns.push_back(std::move(url_pattern)); |
| } |
| return scope_patterns; |
| } |
| |
| proto::os_state::WebAppOsIntegration GenerateRandomWebAppOsIntegration( |
| RandomHelper& random, |
| WebApp& app) { |
| proto::os_state::WebAppOsIntegration 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(random.next_proto_time()); |
| |
| // 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 std::array<proto::os_state::RunOnOsLogin::Mode, 3> |
| run_on_os_login_modes = { |
| proto::os_state::RunOnOsLogin::MODE_NOT_RUN, |
| proto::os_state::RunOnOsLogin::MODE_WINDOWED, |
| proto::os_state::RunOnOsLogin::MODE_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(random.next_proto_time()); |
| |
| auto* data_maskable = menu_info->add_icon_data_maskable(); |
| data_maskable->set_icon_size(16 * random.next_uint(/*bound=*/4)); |
| data_maskable->set_timestamp(random.next_proto_time()); |
| |
| auto* data_monochrome = menu_info->add_icon_data_monochrome(); |
| data_monochrome->set_icon_size(16 * random.next_uint(/*bound=*/4)); |
| data_monochrome->set_timestamp(random.next_proto_time()); |
| } |
| |
| // 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; |
| } |
| |
| std::optional<IsolatedWebAppIntegrityBlockData> CreateIntegrityBlockData( |
| RandomHelper& random) { |
| if (!random.next_bool()) { |
| return std::nullopt; |
| } |
| |
| auto signatures = CreateSignatures(); |
| |
| std::mt19937 rng(random.next_uint()); |
| std::ranges::shuffle(signatures, rng); |
| |
| size_t signatures_count = random.next_uint(signatures.size()) + 1; |
| signatures.erase(signatures.begin() + signatures_count, signatures.end()); |
| |
| return IsolatedWebAppIntegrityBlockData(std::move(signatures)); |
| } |
| |
| std::vector<blink::Manifest::RelatedApplication> |
| CreateRandomRelatedApplications(RandomHelper& random) { |
| std::vector<blink::Manifest::RelatedApplication> related_applications; |
| const std::array<std::string, 7> platforms = { |
| "chrome_web_store", "play", "chromeos_play", "webapp", |
| "windows", "f-droid", "amazon"}; |
| for (int i = random.next_uint(4) + 1; i >= 0; --i) { |
| blink::Manifest::RelatedApplication related_application; |
| related_application.platform = |
| base::UTF8ToUTF16(platforms[random.next_uint(platforms.size())]); |
| bool set_url = random.next_bool(); |
| bool set_id = !set_url || random.next_bool(); |
| if (set_url) { |
| related_application.url = |
| GURL("https://example.com/" + base::NumberToString(i)); |
| } |
| if (set_id) { |
| related_application.id = |
| base::UTF8ToUTF16("id" + base::NumberToString(i)); |
| } |
| related_applications.push_back(std::move(related_application)); |
| } |
| return related_applications; |
| } |
| |
| } // namespace |
| |
| std::unique_ptr<WebApp> CreateWebApp(const GURL& start_url, |
| WebAppManagement::Type source_type) { |
| const webapps::AppId app_id = |
| GenerateAppId(/*manifest_id=*/std::nullopt, start_url); |
| |
| auto web_app = std::make_unique<WebApp>(app_id); |
| web_app->SetStartUrl(start_url); |
| web_app->SetScope(start_url.GetWithoutFilename()); |
| web_app->AddSource(source_type); |
| web_app->SetDisplayMode(blink::mojom::DisplayMode::kStandalone); |
| web_app->SetUserDisplayMode(mojom::UserDisplayMode::kStandalone); |
| web_app->SetName("Name"); |
| // Adding OS integration to this app introduces too many edge cases in tests. |
| // Simply set this to partially installed w/ no os integration, and the |
| // correct OS integration state to match that. |
| web_app->SetInstallState( |
| proto::InstallState::INSTALLED_WITHOUT_OS_INTEGRATION); |
| proto::os_state::WebAppOsIntegration os_state; |
| web_app->SetCurrentOsIntegrationStates(os_state); |
| |
| return web_app; |
| } |
| |
| std::unique_ptr<WebApp> CreateRandomWebApp(CreateRandomWebAppParams params) { |
| RandomHelper random(params.seed, params.non_zero); |
| |
| const std::string seed_str = base::NumberToString(params.seed); |
| std::optional<std::string> relative_manifest_id; |
| if (random.next_bool()) { |
| std::string manifest_id_path = "manifest_id_" + seed_str; |
| if (random.next_bool()) { |
| manifest_id_path += "?query=test"; |
| } |
| if (random.next_bool()) { |
| manifest_id_path += "#fragment"; |
| } |
| relative_manifest_id = manifest_id_path; |
| } |
| std::string scope_path = "scope" + seed_str + "/"; |
| if (random.next_bool()) { |
| scope_path += "?query=test"; |
| } |
| if (random.next_bool()) { |
| scope_path += "#fragment"; |
| } |
| const GURL scope(params.base_url.Resolve(scope_path)); |
| const GURL start_url = scope.Resolve("start" + seed_str); |
| const webapps::AppId app_id = GenerateAppId(relative_manifest_id, start_url); |
| |
| const std::string name = "Name" + seed_str; |
| const std::string description = "Description" + seed_str; |
| const std::optional<SkColor> theme_color = random.next_uint(); |
| std::optional<SkColor> dark_mode_theme_color; |
| const std::optional<SkColor> background_color = random.next_uint(); |
| std::optional<SkColor> dark_mode_background_color; |
| const std::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 (params.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::kUserInstalled); |
| management_types.push_back(WebAppManagement::kUserInstalled); |
| } |
| 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::kIwaShimlessRma); |
| management_types.push_back(WebAppManagement::kIwaShimlessRma); |
| } |
| if (random.next_bool()) { |
| app->AddSource(WebAppManagement::kIwaPolicy); |
| management_types.push_back(WebAppManagement::kIwaPolicy); |
| } |
| if (random.next_bool()) { |
| app->AddSource(WebAppManagement::kIwaUserInstalled); |
| management_types.push_back(WebAppManagement::kIwaUserInstalled); |
| } |
| 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); |
| } |
| if (random.next_bool()) { |
| app->AddSource(WebAppManagement::kApsDefault); |
| management_types.push_back(WebAppManagement::kApsDefault); |
| } |
| |
| // Must always be at least one source. |
| if (!app->HasAnySources()) { |
| app->AddSource(WebAppManagement::kUserInstalled); |
| management_types.push_back(WebAppManagement::kUserInstalled); |
| } |
| |
| 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); |
| if (relative_manifest_id) { |
| app->SetManifestId( |
| GenerateManifestId(relative_manifest_id.value(), start_url)); |
| } |
| 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->SetInstallState(random.next_enum<proto::InstallState, |
| /*min=*/proto::InstallState_MIN, |
| /*max=*/ |
| proto::InstallState_MAX>()); |
| app->SetIsFromSyncAndPendingInstallation(random.next_bool()); |
| |
| const std::array<sync_pb::WebAppSpecifics::UserDisplayMode, 3> |
| user_display_modes = { |
| sync_pb::WebAppSpecifics_UserDisplayMode_BROWSER, |
| sync_pb::WebAppSpecifics_UserDisplayMode_STANDALONE, |
| sync_pb::WebAppSpecifics_UserDisplayMode_TABBED, |
| }; |
| // Explicitly set a UserDisplayMode for each platform (instead of calling |
| // `SetUserDisplayMode/SetPlatformSpecificUserDisplayMode` which sets the |
| // current platform's value only) so the test expectations are consistent |
| // across platforms. |
| { |
| // Copy proto, retaining existing fields (including unknown fields). |
| sync_pb::WebAppSpecifics sync_proto = app->sync_proto(); |
| sync_proto.set_user_display_mode_default( |
| user_display_modes[random.next_uint(3)]); |
| sync_proto.set_user_display_mode_cros( |
| user_display_modes[random.next_uint(3)]); |
| CHECK(HasCurrentPlatformUserDisplayMode(sync_proto)); |
| |
| if (random.next_bool()) { |
| sync_proto.set_user_launch_ordinal( |
| syncer::StringOrdinal::CreateInitialOrdinal().ToInternalValue()); |
| } |
| |
| if (random.next_bool()) { |
| sync_proto.set_user_page_ordinal( |
| syncer::StringOrdinal::CreateInitialOrdinal().ToInternalValue()); |
| } |
| |
| app->SetSyncProto(std::move(sync_proto)); |
| } |
| |
| app->SetLastBadgingTime(random.next_time()); |
| |
| app->SetLastLaunchTime(random.next_time()); |
| |
| app->SetFirstInstallTime(random.next_time()); |
| |
| const std::array<DisplayMode, 4> display_modes = { |
| 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 = params.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())); |
| } |
| |
| webapps::WebappInstallSource install_source = |
| random.next_enum<webapps::WebappInstallSource, 0>(); |
| app->SetLatestInstallSource(install_source); |
| |
| app->SetProtocolHandlers(CreateRandomProtocolHandlers(random.next_uint())); |
| app->SetScopeExtensions( |
| CreateRandomScopeExtensions(random.next_uint(), random)); |
| |
| ScopeExtensions validated_scope_extensions; |
| std::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); |
| |
| app->SetIsDiyApp(random.next_bool()); |
| app->SetWasShortcutApp(random.next_bool()); |
| |
| 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_items = static_cast<int>(random.next_uint(4)) + 1; |
| auto item_infos = |
| CreateRandomShortcutsMenuItemInfos(scope, num_shortcut_items, random); |
| auto icons_sizes = |
| CreateRandomDownloadedShortcutsMenuIconsSizes(num_shortcut_items, random); |
| CHECK_EQ(item_infos.size(), icons_sizes.size()); |
| for (int i = 0; i < num_shortcut_items; ++i) { |
| item_infos[i].downloaded_icon_sizes = std::move(icons_sizes[i]); |
| } |
| icons_sizes.clear(); |
| app->SetShortcutsMenuInfo(std::move(item_infos)); |
| |
| app->SetManifestUrl( |
| params.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); |
| |
| { |
| // Copy proto, retaining existing fields (including unknown fields). |
| sync_pb::WebAppSpecifics sync_proto = app->sync_proto(); |
| sync_proto.set_name("Sync" + name); |
| if (synced_theme_color.has_value()) { |
| sync_proto.set_theme_color(synced_theme_color.value()); |
| } |
| sync_proto.set_scope(app->scope().spec()); |
| for (const apps::IconInfo& icon_info : app->manifest_icons()) { |
| *(sync_proto.add_icon_infos()) = AppIconInfoToSyncProto(icon_info); |
| } |
| app->SetSyncProto(std::move(sync_proto)); |
| } |
| |
| if (random.next_bool()) { |
| app->SetLaunchHandler( |
| LaunchHandler{random.next_enum<LaunchHandler::ClientMode>()}); |
| } |
| |
| app->SetManifestUpdateTime(random.next_time()); |
| |
| if (random.next_bool()) { |
| app->SetParentAppId(base::NumberToString(random.next_uint())); |
| } |
| |
| if (random.next_bool()) { |
| app->SetPermissionsPolicy(CreateRandomPermissionsPolicy(random)); |
| } |
| |
| if (IsChromeOsDataMandatory()) { |
| // Use a separate random generator for CrOS so the result is deterministic |
| // across cros and non-cros builds. |
| RandomHelper cros_random(params.seed, params.non_zero); |
| auto chromeos_data = std::make_optional<WebAppChromeOsData>(); |
| chromeos_data->show_in_launcher = cros_random.next_bool(); |
| chromeos_data->show_in_search_and_shelf = 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 and shimless RMA apps cannot be OEM |
| // installed. |
| if (app->IsSystemApp() || app->IsIwaShimlessRmaApp()) { |
| 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 || |
| type == WebAppManagement::kUserInstalled || |
| WebAppManagement::IsIwaType(type)) { |
| 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( |
| params.base_url.Resolve("installer1_" + seed_str + "/")); |
| } |
| if (random.next_bool()) { |
| install_urls.emplace( |
| params.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); |
| } |
| if (random.next_bool()) { |
| home_tab_params.scope_patterns = CreateRandomScopePatterns(random); |
| } |
| tab_strip.home_tab = std::move(home_tab_params); |
| } else { |
| tab_strip.home_tab = |
| random.next_enum<blink::mojom::TabStripMemberVisibility>(); |
| } |
| |
| 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; |
| |
| app->SetTabStrip(std::move(tab_strip)); |
| } |
| |
| app->SetAlwaysShowToolbarInFullscreen(random.next_bool()); |
| |
| // Note: Even though OS integration sometimes should be guaranteed to be not |
| // there or there based on the install state or |
| // is_from_sync_and_pending_installation, Because |
| // current_os_integration_states is set in a two-phase-commit style, it is |
| // possible & normal for it to be out of sync with what is desired. So allow |
| // it to be set to any value. |
| app->SetCurrentOsIntegrationStates( |
| GenerateRandomWebAppOsIntegration(random, *app)); |
| |
| if (random.next_bool()) { |
| bool dev_mode = random.next_bool(); |
| auto get_location_type = [&seed_str, &random, |
| &dev_mode]() -> IsolatedWebAppStorageLocation { |
| if (!dev_mode) { |
| return IwaStorageOwnedBundle{ |
| base32::Base32Encode(base::as_byte_span(seed_str), |
| base32::Base32EncodePolicy::OMIT_PADDING), |
| /*dev_mode=*/false}; |
| } else { |
| constexpr size_t kNumLocationTypes = |
| std::variant_size<IsolatedWebAppStorageLocation::Variant>::value; |
| std::array<IsolatedWebAppStorageLocation, kNumLocationTypes> |
| location_types = { |
| IwaStorageOwnedBundle{ |
| base32::Base32Encode( |
| base::as_byte_span(seed_str), |
| base32::Base32EncodePolicy::OMIT_PADDING), |
| /*dev_mode=*/true}, |
| IwaStorageUnownedBundle{ |
| base::FilePath::FromUTF8Unsafe(seed_str)}, |
| IwaStorageProxy{url::Origin::Create( |
| GURL(base::StrCat({"https://proxy-", seed_str, ".com/"})))}, |
| }; |
| return location_types.at(random.next_uint(kNumLocationTypes)); |
| } |
| }; |
| |
| base::Version version = base::Version({ |
| random.next_uint(UINT32_MAX - 1U), |
| random.next_uint(), |
| random.next_uint(), |
| }); |
| |
| auto idb = IsolationData::Builder(get_location_type(), version); |
| std::optional<IsolatedWebAppIntegrityBlockData> integrity_block_data = |
| CreateIntegrityBlockData(random); |
| if (integrity_block_data) { |
| idb.SetIntegrityBlockData(std::move(*integrity_block_data)); |
| } |
| |
| if (random.next_bool()) { |
| idb.SetControlledFramePartitions({"partition_name"}); |
| } |
| if (random.next_bool()) { |
| base::Version pending_version = [&] { |
| if (random.next_bool()) { |
| // Case where `pending_version == version`. Useful for validating key |
| // rotation scenarios. |
| return version; |
| } |
| // Otherwise, create `pending_version > version`. |
| uint32_t major_version = version.components()[0]; |
| CHECK_LT(major_version, UINT32_MAX - 1U); |
| uint32_t delta = random.next_uint(UINT32_MAX - 1U - major_version) + 1; |
| // `major_version` + `delta` < UINT32_MAX. |
| return base::Version( |
| {major_version + delta, random.next_uint(), random.next_uint()}); |
| }(); |
| CHECK_GE(pending_version, version); |
| IsolationData::PendingUpdateInfo pending_update_info( |
| get_location_type(), pending_version, integrity_block_data); |
| idb.SetPendingUpdateInfo(std::move(pending_update_info)); |
| } |
| if (dev_mode && random.next_bool()) { |
| idb.SetUpdateManifestUrl(GURL("https://update-manifest.com")); |
| idb.SetUpdateChannel(UpdateChannel::default_channel()); |
| } |
| app->SetIsolationData(std::move(idb).Build()); |
| } |
| |
| app->SetLinkCapturingUserPreference(NEXT_PROTO_ENUM( |
| random, proto::LinkCapturingUserPreference, /*skip_zero=*/false)); |
| |
| app->SetLatestInstallTime(random.next_time()); |
| if (random.next_bool()) { |
| proto::GeneratedIconFix generated_icon_fix; |
| generated_icon_fix.set_source(NEXT_PROTO_ENUM( |
| random, proto::GeneratedIconFixSource, /*skip_zero=*/true)); |
| generated_icon_fix.set_window_start_time(random.next_proto_time()); |
| if (random.next_bool()) { |
| generated_icon_fix.set_last_attempt_time(random.next_proto_time()); |
| } |
| generated_icon_fix.set_attempt_count(random.next_uint(100)); |
| app->SetGeneratedIconFix(generated_icon_fix); |
| } |
| |
| app->SetSupportedLinksOfferIgnoreCount(random.next_uint()); |
| app->SetSupportedLinksOfferDismissCount(random.next_uint()); |
| app->SetRelatedApplications(CreateRandomRelatedApplications(random)); |
| app->SetDiyAppIconsMaskedOnMac(random.next_bool()); |
| |
| return app; |
| } |
| |
| void TestAcceptDialogCallback( |
| base::WeakPtr<WebAppScreenshotFetcher>, |
| content::WebContents* initiator_web_contents, |
| std::unique_ptr<WebAppInstallInfo> web_app_info, |
| WebAppInstallationAcceptanceCallback acceptance_callback) { |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(std::move(acceptance_callback), true /*accept*/, |
| std::move(web_app_info))); |
| } |
| |
| void TestDeclineDialogCallback( |
| base::WeakPtr<WebAppScreenshotFetcher>, |
| content::WebContents* initiator_web_contents, |
| std::unique_ptr<WebAppInstallInfo> web_app_info, |
| WebAppInstallationAcceptanceCallback acceptance_callback) { |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(std::move(acceptance_callback), |
| false /*accept*/, std::move(web_app_info))); |
| } |
| |
| // TODO(b/329703817): Make this smarter by waiting for a specific dialog, and |
| // then triggering accept on the dialog. |
| webapps::AppId InstallPwaForCurrentUrl(Browser* browser) { |
| // Depending on the installability criteria, different dialogs can be used. |
| SetAutoAcceptWebAppDialogForTesting(true, true); |
| auto auto_accept_pwa_install_confirmation = |
| SetAutoAcceptPWAInstallConfirmationForTesting(); |
| SetAutoAcceptDiyAppsInstallDialogForTesting(true); |
| WebAppTestInstallWithOsHooksObserver observer(browser->profile()); |
| observer.BeginListening(); |
| CHECK(chrome::ExecuteCommand(browser, IDC_INSTALL_PWA)); |
| webapps::AppId app_id = observer.Wait(); |
| SetAutoAcceptWebAppDialogForTesting(false, false); |
| SetAutoAcceptDiyAppsInstallDialogForTesting(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, std::string_view 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 webapps::AppId& app_id, |
| const GURL& url, |
| const ExternalInstallSource& source) { |
| ScopedRegistryUpdate update = sync_bridge->BeginUpdate(); |
| 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); |
| } |
| |
| void AddInstallUrlAndPlaceholderData(PrefService* pref_service, |
| WebAppSyncBridge* sync_bridge, |
| const webapps::AppId& app_id, |
| const GURL& url, |
| const ExternalInstallSource& source, |
| bool is_placeholder) { |
| ScopedRegistryUpdate update = sync_bridge->BeginUpdate(); |
| 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); |
| } |
| |
| void SynchronizeOsIntegration(Profile* profile, |
| const webapps::AppId& app_id, |
| std::optional<SynchronizeOsOptions> options) { |
| base::test::TestFuture<void> sync_future; |
| WebAppProvider::GetForTest(profile)->scheduler().SynchronizeOsIntegration( |
| app_id, sync_future.GetCallback(), options); |
| EXPECT_TRUE(sync_future.Wait()); |
| } |
| |
| std::vector<web_package::SignedWebBundleSignatureInfo> CreateSignatures() { |
| std::vector<web_package::SignedWebBundleSignatureInfo> signatures; |
| |
| // EcdsaP256SHA256: |
| { |
| auto public_key = *web_package::EcdsaP256PublicKey::Create( |
| *base::Base64Decode(kEcdsaP256PublicKeyBase64)); |
| std::vector<uint8_t> data; |
| CHECK(base::HexStringToBytes(kEcdsaP256SHA256SignatureHex, &data)); |
| auto signature = *web_package::EcdsaP256SHA256Signature::Create(data); |
| signatures.push_back( |
| web_package::SignedWebBundleSignatureInfoEcdsaP256SHA256( |
| std::move(public_key), std::move(signature))); |
| } |
| |
| // Ed25519: |
| { |
| auto public_key = *web_package::Ed25519PublicKey::Create( |
| *base::Base64Decode(kEd25519PublicKeyBase64)); |
| std::vector<uint8_t> data; |
| CHECK(base::HexStringToBytes(kEd25519SignatureHex, &data)); |
| auto signature = *web_package::Ed25519Signature::Create(data); |
| signatures.push_back(web_package::SignedWebBundleSignatureInfoEd25519( |
| std::move(public_key), std::move(signature))); |
| } |
| |
| // Unknown: |
| signatures.push_back(web_package::SignedWebBundleSignatureInfoUnknown()); |
| return signatures; |
| } |
| |
| } // namespace web_app::test |