| // Copyright 2019 The Chromium Authors | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "chrome/browser/web_applications/web_app.h" | 
 |  | 
 | #include <memory> | 
 | #include <string> | 
 |  | 
 | #include "base/check.h" | 
 | #include "base/command_line.h" | 
 | #include "base/files/file.h" | 
 | #include "base/files/file_path.h" | 
 | #include "base/files/file_util.h" | 
 | #include "base/json/json_reader.h" | 
 | #include "base/json/json_writer.h" | 
 | #include "base/path_service.h" | 
 | #include "base/strings/string_piece.h" | 
 | #include "base/strings/string_piece_forward.h" | 
 | #include "base/values.h" | 
 | #include "build/chromeos_buildflags.h" | 
 | #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_location.h" | 
 | #include "chrome/browser/web_applications/test/web_app_test_utils.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_utils.h" | 
 | #include "chrome/common/chrome_paths.h" | 
 | #include "testing/gtest/include/gtest/gtest.h" | 
 | #include "third_party/blink/public/common/permissions_policy/origin_with_possible_wildcards.h" | 
 | #include "third_party/blink/public/common/permissions_policy/permissions_policy_declaration.h" | 
 | #include "third_party/blink/public/mojom/permissions_policy/permissions_policy_feature.mojom.h" | 
 | #include "url/gurl.h" | 
 | #include "url/origin.h" | 
 |  | 
 | namespace web_app { | 
 |  | 
 | namespace { | 
 |  | 
 | constexpr base::StringPiece kGenerateExpectationsMessage = R"( | 
 | In order to regenerate expectations run | 
 | the following command: | 
 |   out/<dir>/unit_tests \ | 
 |     --gtest_filter="WebAppTest.*" \ | 
 |     --rebaseline-web-app-expectations | 
 | )"; | 
 |  | 
 | base::Value WebAppToPlatformAgnosticDebugValue( | 
 |     std::unique_ptr<WebApp> web_app) { | 
 |   return web_app->AsDebugValueWithOnlyPlatformAgnosticFields(); | 
 | } | 
 |  | 
 | base::FilePath GetTestDataDir() { | 
 |   base::FilePath test_data_dir; | 
 |   CHECK(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir)); | 
 |   return test_data_dir; | 
 | } | 
 |  | 
 | base::FilePath GetPathRelativeToTestDataDir( | 
 |     const base::FilePath absolute_path) { | 
 |   base::FilePath relative_path; | 
 |   GetTestDataDir().AppendRelativePath(absolute_path, &relative_path); | 
 |   return relative_path; | 
 | } | 
 |  | 
 | base::FilePath GetPathToTestFile(base::StringPiece filename) { | 
 |   return GetTestDataDir().AppendASCII("web_apps").AppendASCII(filename); | 
 | } | 
 |  | 
 | std::string GetContentsOrDie(const base::FilePath& filepath) { | 
 |   std::string contents; | 
 |   CHECK(base::ReadFileToString(filepath, &contents)); | 
 |   return contents; | 
 | } | 
 |  | 
 | void SetContentsOrDie(const base::FilePath& filepath, | 
 |                       base::StringPiece contents) { | 
 |   CHECK(base::WriteFile(filepath, contents)); | 
 | } | 
 |  | 
 | std::string SerializeValueToJsonOrDie(const base::Value& value) { | 
 |   std::string contents; | 
 |   CHECK(base::JSONWriter::WriteWithOptions( | 
 |       value, base::JSONWriter::Options::OPTIONS_PRETTY_PRINT, &contents)); | 
 |   return contents; | 
 | } | 
 |  | 
 | base::Value DeserializeValueFromJsonOrDie(base::StringPiece json) { | 
 |   absl::optional<base::Value> value = base::JSONReader::Read(json); | 
 |   CHECK(value.has_value()); | 
 |   return *std::move(value); | 
 | } | 
 |  | 
 | bool IsRebaseline() { | 
 |   const base::CommandLine& command_line = | 
 |       *base::CommandLine::ForCurrentProcess(); | 
 |   return command_line.HasSwitch("rebaseline-web-app-expectations"); | 
 | } | 
 |  | 
 | void SaveExpectationsContentsOrDie(const base::FilePath path, | 
 |                                    base::StringPiece contents) { | 
 |   const std::string current_contents = GetContentsOrDie(path); | 
 |  | 
 |   const base::FilePath test_data_dir_relative_path = | 
 |       GetPathRelativeToTestDataDir(path); | 
 |  | 
 |   if (current_contents != contents) { | 
 |     LOG(INFO) << "New content is generated for " << test_data_dir_relative_path; | 
 |   } else { | 
 |     LOG(INFO) << "No new content is generated for " | 
 |               << test_data_dir_relative_path; | 
 |   } | 
 |  | 
 |   SetContentsOrDie(path, contents); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | TEST(WebAppTest, HasAnySources) { | 
 |   WebApp app{GenerateAppId(/*manifest_id_path=*/absl::nullopt, | 
 |                            GURL("https://example.com"))}; | 
 |  | 
 |   EXPECT_FALSE(app.HasAnySources()); | 
 |   for (WebAppManagement::Type source : WebAppManagementTypes::All()) { | 
 |     app.AddSource(source); | 
 |     EXPECT_TRUE(app.HasAnySources()); | 
 |   } | 
 |  | 
 |   for (WebAppManagement::Type source : WebAppManagementTypes::All()) { | 
 |     EXPECT_TRUE(app.HasAnySources()); | 
 |     app.RemoveSource(source); | 
 |   } | 
 |   EXPECT_FALSE(app.HasAnySources()); | 
 | } | 
 |  | 
 | TEST(WebAppTest, HasOnlySource) { | 
 |   WebApp app{GenerateAppId(/*manifest_id_path=*/absl::nullopt, | 
 |                            GURL("https://example.com"))}; | 
 |  | 
 |   for (WebAppManagement::Type source : WebAppManagementTypes::All()) { | 
 |     app.AddSource(source); | 
 |     EXPECT_TRUE(app.HasOnlySource(source)); | 
 |  | 
 |     app.RemoveSource(source); | 
 |     EXPECT_FALSE(app.HasOnlySource(source)); | 
 |   } | 
 |  | 
 |   app.AddSource(WebAppManagement::kMinValue); | 
 |   EXPECT_TRUE(app.HasOnlySource(WebAppManagement::kMinValue)); | 
 |  | 
 |   for (WebAppManagement::Type source : WebAppManagementTypes::All()) { | 
 |     if (source == WebAppManagement::kMinValue) { | 
 |       continue; | 
 |     } | 
 |     app.AddSource(source); | 
 |     EXPECT_FALSE(app.HasOnlySource(source)); | 
 |     EXPECT_FALSE(app.HasOnlySource(WebAppManagement::kMinValue)); | 
 |   } | 
 |  | 
 |   for (WebAppManagement::Type source : WebAppManagementTypes::All()) { | 
 |     if (source == WebAppManagement::kMinValue) { | 
 |       continue; | 
 |     } | 
 |     EXPECT_FALSE(app.HasOnlySource(WebAppManagement::kMinValue)); | 
 |     app.RemoveSource(source); | 
 |     EXPECT_FALSE(app.HasOnlySource(source)); | 
 |   } | 
 |  | 
 |   EXPECT_TRUE(app.HasOnlySource(WebAppManagement::kMinValue)); | 
 |   app.RemoveSource(WebAppManagement::kMinValue); | 
 |   EXPECT_FALSE(app.HasOnlySource(WebAppManagement::kMinValue)); | 
 |   EXPECT_FALSE(app.HasAnySources()); | 
 | } | 
 |  | 
 | TEST(WebAppTest, WasInstalledByUser) { | 
 |   WebApp app{GenerateAppId(/*manifest_id_path=*/absl::nullopt, | 
 |                            GURL("https://example.com"))}; | 
 |  | 
 |   app.AddSource(WebAppManagement::kSync); | 
 |   EXPECT_TRUE(app.WasInstalledByUser()); | 
 |  | 
 |   app.AddSource(WebAppManagement::kWebAppStore); | 
 |   EXPECT_TRUE(app.WasInstalledByUser()); | 
 |  | 
 |   app.RemoveSource(WebAppManagement::kSync); | 
 |   EXPECT_TRUE(app.WasInstalledByUser()); | 
 |  | 
 |   app.RemoveSource(WebAppManagement::kWebAppStore); | 
 |   EXPECT_FALSE(app.WasInstalledByUser()); | 
 |  | 
 |   app.AddSource(WebAppManagement::kOneDriveIntegration); | 
 |   EXPECT_TRUE(app.WasInstalledByUser()); | 
 |  | 
 |   app.RemoveSource(WebAppManagement::kOneDriveIntegration); | 
 |   EXPECT_FALSE(app.WasInstalledByUser()); | 
 |  | 
 |   app.AddSource(WebAppManagement::kDefault); | 
 |   EXPECT_FALSE(app.WasInstalledByUser()); | 
 |  | 
 |   app.AddSource(WebAppManagement::kSystem); | 
 |   EXPECT_FALSE(app.WasInstalledByUser()); | 
 |  | 
 |   app.AddSource(WebAppManagement::kKiosk); | 
 |   EXPECT_FALSE(app.WasInstalledByUser()); | 
 |  | 
 |   app.AddSource(WebAppManagement::kPolicy); | 
 |   EXPECT_FALSE(app.WasInstalledByUser()); | 
 |  | 
 |   app.AddSource(WebAppManagement::kSubApp); | 
 |   EXPECT_FALSE(app.WasInstalledByUser()); | 
 |  | 
 |   app.RemoveSource(WebAppManagement::kDefault); | 
 |   EXPECT_FALSE(app.WasInstalledByUser()); | 
 |  | 
 |   app.RemoveSource(WebAppManagement::kSystem); | 
 |   EXPECT_FALSE(app.WasInstalledByUser()); | 
 |  | 
 |   app.RemoveSource(WebAppManagement::kKiosk); | 
 |   EXPECT_FALSE(app.WasInstalledByUser()); | 
 |  | 
 |   app.RemoveSource(WebAppManagement::kPolicy); | 
 |   EXPECT_FALSE(app.WasInstalledByUser()); | 
 |  | 
 |   app.RemoveSource(WebAppManagement::kSubApp); | 
 |   EXPECT_FALSE(app.WasInstalledByUser()); | 
 | } | 
 |  | 
 | TEST(WebAppTest, CanUserUninstallWebApp) { | 
 |   WebApp app{GenerateAppId(/*manifest_id_path=*/absl::nullopt, | 
 |                            GURL("https://example.com"))}; | 
 |  | 
 |   app.AddSource(WebAppManagement::kDefault); | 
 |   EXPECT_TRUE(app.IsPreinstalledApp()); | 
 |   EXPECT_TRUE(app.CanUserUninstallWebApp()); | 
 |  | 
 |   app.AddSource(WebAppManagement::kSync); | 
 |   EXPECT_TRUE(app.CanUserUninstallWebApp()); | 
 |   app.AddSource(WebAppManagement::kWebAppStore); | 
 |   EXPECT_TRUE(app.CanUserUninstallWebApp()); | 
 |   app.AddSource(WebAppManagement::kSubApp); | 
 |   EXPECT_TRUE(app.CanUserUninstallWebApp()); | 
 |   app.AddSource(WebAppManagement::kOneDriveIntegration); | 
 |   EXPECT_TRUE(app.CanUserUninstallWebApp()); | 
 |  | 
 |   app.AddSource(WebAppManagement::kPolicy); | 
 |   EXPECT_FALSE(app.CanUserUninstallWebApp()); | 
 |   app.AddSource(WebAppManagement::kKiosk); | 
 |   EXPECT_FALSE(app.CanUserUninstallWebApp()); | 
 |   app.AddSource(WebAppManagement::kSystem); | 
 |   EXPECT_FALSE(app.CanUserUninstallWebApp()); | 
 |  | 
 |   app.RemoveSource(WebAppManagement::kSync); | 
 |   EXPECT_FALSE(app.CanUserUninstallWebApp()); | 
 |   app.RemoveSource(WebAppManagement::kWebAppStore); | 
 |   EXPECT_FALSE(app.CanUserUninstallWebApp()); | 
 |   app.RemoveSource(WebAppManagement::kSubApp); | 
 |   EXPECT_FALSE(app.CanUserUninstallWebApp()); | 
 |  | 
 |   app.RemoveSource(WebAppManagement::kSystem); | 
 |   EXPECT_FALSE(app.CanUserUninstallWebApp()); | 
 |  | 
 |   app.RemoveSource(WebAppManagement::kKiosk); | 
 |   EXPECT_FALSE(app.CanUserUninstallWebApp()); | 
 |  | 
 |   app.RemoveSource(WebAppManagement::kPolicy); | 
 |   EXPECT_TRUE(app.CanUserUninstallWebApp()); | 
 |  | 
 |   app.RemoveSource(WebAppManagement::kOneDriveIntegration); | 
 |   EXPECT_TRUE(app.CanUserUninstallWebApp()); | 
 |  | 
 |   EXPECT_TRUE(app.IsPreinstalledApp()); | 
 |   app.RemoveSource(WebAppManagement::kDefault); | 
 |   EXPECT_FALSE(app.IsPreinstalledApp()); | 
 | } | 
 |  | 
 | TEST(WebAppTest, EmptyAppAsDebugValue) { | 
 |   const base::FilePath path_to_test_file = | 
 |       GetPathToTestFile("empty_web_app.json"); | 
 |   const base::Value web_app_debug_value = | 
 |       WebAppToPlatformAgnosticDebugValue(std::make_unique<WebApp>("empty_app")); | 
 |  | 
 |   if (IsRebaseline()) { | 
 |     LOG(INFO) << "Generating expectations empty web app unit test in " | 
 |               << GetPathRelativeToTestDataDir(path_to_test_file); | 
 |     SaveExpectationsContentsOrDie( | 
 |         path_to_test_file, SerializeValueToJsonOrDie(web_app_debug_value)); | 
 |     return; | 
 |   } | 
 |  | 
 |   EXPECT_EQ(DeserializeValueFromJsonOrDie(GetContentsOrDie(path_to_test_file)), | 
 |             web_app_debug_value) | 
 |       << "Debug value of empty web app is unexpected. " | 
 |       << kGenerateExpectationsMessage; | 
 | } | 
 |  | 
 | // The values of the SampleApp are randomly generated. This test is mainly | 
 | // checking that the output is formatted well and doesn't crash. Exact field | 
 | // values are unimportant. | 
 | // | 
 | // If you have made changes and this test is failing, run the test with | 
 | // `--rebaseline-web-app-expectations` to generate a new `sample_web_app.json`. | 
 | TEST(WebAppTest, SampleAppAsDebugValue) { | 
 |   const base::FilePath path_to_test_file = | 
 |       GetPathToTestFile("sample_web_app.json"); | 
 |   const base::Value web_app_debug_value = WebAppToPlatformAgnosticDebugValue( | 
 |       test::CreateRandomWebApp({.seed = 1234, .non_zero = true})); | 
 |  | 
 |   if (IsRebaseline()) { | 
 |     LOG(INFO) << "Generating expectations sample web app unit test in " | 
 |               << GetPathRelativeToTestDataDir(path_to_test_file); | 
 |     SaveExpectationsContentsOrDie( | 
 |         path_to_test_file, SerializeValueToJsonOrDie(web_app_debug_value)); | 
 |     return; | 
 |   } | 
 |  | 
 |   EXPECT_EQ(DeserializeValueFromJsonOrDie(GetContentsOrDie(path_to_test_file)), | 
 |             web_app_debug_value) | 
 |       << "Debug value of sample web app is unexpected. " | 
 |       << kGenerateExpectationsMessage; | 
 | } | 
 |  | 
 | TEST(WebAppTest, RandomAppAsDebugValue_NoCrash) { | 
 |   for (uint32_t seed = 0; seed < 1000; ++seed) { | 
 |     const base::Value web_app_debug_value = | 
 |         test::CreateRandomWebApp({.seed = seed})->AsDebugValue(); | 
 |  | 
 |     EXPECT_TRUE(web_app_debug_value.is_dict()); | 
 |     EXPECT_TRUE(base::ToString(web_app_debug_value).length() > 10); | 
 |   } | 
 | } | 
 |  | 
 | TEST(WebAppTest, IsolationDataStartsEmpty) { | 
 |   WebApp app{GenerateAppId(/*manifest_id_path=*/absl::nullopt, | 
 |                            GURL("https://example.com"))}; | 
 |  | 
 |   EXPECT_FALSE(app.isolation_data().has_value()); | 
 | } | 
 |  | 
 | TEST(WebAppTest, IsolationDataDebugValue) { | 
 |   WebApp app{GenerateAppId(/*manifest_id_path=*/absl::nullopt, | 
 |                            GURL("https://example.com"))}; | 
 |   app.SetIsolationData(WebApp::IsolationData( | 
 |       InstalledBundle{.path = base::FilePath(FILE_PATH_LITERAL("random_path"))}, | 
 |       base::Version("1.0.0"))); | 
 |  | 
 |   EXPECT_TRUE(app.isolation_data().has_value()); | 
 |  | 
 |   base::Value expected_isolation_data = base::JSONReader::Read(R"|({ | 
 |         "isolated_web_app_location": { | 
 |           "installed_bundle": { | 
 |             "path": "random_path" | 
 |           } | 
 |         }, | 
 |         "version": "1.0.0", | 
 |         "controlled_frame_partitions (on-disk)": [], | 
 |         "pending_update_info": null | 
 |       })|") | 
 |                                             .value(); | 
 |  | 
 |   base::Value::Dict debug_app = app.AsDebugValue().GetDict().Clone(); | 
 |   base::Value::Dict* debug_isolation_data = | 
 |       debug_app.FindDict("isolation_data"); | 
 |   EXPECT_TRUE(debug_isolation_data != nullptr); | 
 |   EXPECT_EQ(*debug_isolation_data, expected_isolation_data); | 
 | } | 
 |  | 
 | TEST(WebAppTest, IsolationDataPendingUpdateInfoDebugValue) { | 
 |   WebApp app{GenerateAppId(/*manifest_id_path=*/absl::nullopt, | 
 |                            GURL("https://example.com"))}; | 
 |   app.SetIsolationData(WebApp::IsolationData( | 
 |       InstalledBundle{.path = base::FilePath(FILE_PATH_LITERAL("random_path"))}, | 
 |       base::Version("1.0.0"), {}, | 
 |       WebApp::IsolationData::PendingUpdateInfo( | 
 |           InstalledBundle{ | 
 |               .path = base::FilePath(FILE_PATH_LITERAL("another_path"))}, | 
 |           base::Version("2.0.0")))); | 
 |  | 
 |   EXPECT_TRUE(app.isolation_data().has_value()); | 
 |  | 
 |   base::Value expected_isolation_data = base::JSONReader::Read(R"|({ | 
 |         "isolated_web_app_location": { | 
 |           "installed_bundle": { | 
 |             "path": "random_path" | 
 |           } | 
 |         }, | 
 |         "version": "1.0.0", | 
 |         "controlled_frame_partitions (on-disk)": [], | 
 |         "pending_update_info": { | 
 |           "isolated_web_app_location": { | 
 |             "installed_bundle": { | 
 |               "path": "another_path" | 
 |             } | 
 |           }, | 
 |           "version": "2.0.0" | 
 |         } | 
 |       })|") | 
 |                                             .value(); | 
 |  | 
 |   base::Value::Dict debug_app = app.AsDebugValue().GetDict().Clone(); | 
 |   base::Value::Dict* debug_isolation_data = | 
 |       debug_app.FindDict("isolation_data"); | 
 |   EXPECT_TRUE(debug_isolation_data != nullptr); | 
 |   EXPECT_EQ(*debug_isolation_data, expected_isolation_data); | 
 | } | 
 |  | 
 | TEST(WebAppTest, PermissionsPolicyDebugValue) { | 
 |   WebApp app{GenerateAppId(/*manifest_id_path=*/absl::nullopt, | 
 |                            GURL("https://example.com"))}; | 
 |   app.SetPermissionsPolicy({ | 
 |       {blink::mojom::PermissionsPolicyFeature::kGyroscope, | 
 |        /*allowed_origins=*/{}, | 
 |        /*self_if_matches=*/absl::nullopt, | 
 |        /*matches_all_origins=*/false, | 
 |        /*matches_opaque_src=*/true}, | 
 |       {blink::mojom::PermissionsPolicyFeature::kGeolocation, | 
 |        /*allowed_origins=*/{}, | 
 |        /*self_if_matches=*/absl::nullopt, | 
 |        /*matches_all_origins=*/true, | 
 |        /*matches_opaque_src=*/false}, | 
 |       {blink::mojom::PermissionsPolicyFeature::kGamepad, | 
 |        {*blink::OriginWithPossibleWildcards::FromOriginAndWildcardsForTest( | 
 |             url::Origin::Create(GURL("https://example.com")), | 
 |             /*has_subdomain_wildcard=*/false), | 
 |         *blink::OriginWithPossibleWildcards::FromOriginAndWildcardsForTest( | 
 |             url::Origin::Create(GURL("https://example.net")), | 
 |             /*has_subdomain_wildcard=*/true)}, | 
 |        /*self_if_matches=*/absl::nullopt, | 
 |        /*matches_all_origins=*/false, | 
 |        /*matches_opaque_src=*/false}, | 
 |   }); | 
 |  | 
 |   EXPECT_TRUE(!app.permissions_policy().empty()); | 
 |  | 
 |   base::Value expected_permissions_policy = base::JSONReader::Read(R"([ | 
 |         { | 
 |           "allowed_origins": [  ], | 
 |           "feature": "gyroscope", | 
 |           "matches_all_origins": false, | 
 |           "matches_opaque_src": true | 
 |         } | 
 |         , { | 
 |           "allowed_origins": [  ], | 
 |           "feature": "geolocation", | 
 |           "matches_all_origins": true, | 
 |           "matches_opaque_src": false | 
 |         } | 
 |         , { | 
 |           "allowed_origins": [ "https://example.com", "https://*.example.net" ], | 
 |           "feature": "gamepad", | 
 |           "matches_all_origins": false, | 
 |           "matches_opaque_src": false | 
 |         } | 
 |       ])") | 
 |                                                 .value(); | 
 |  | 
 |   base::Value::Dict debug_app = app.AsDebugValue().GetDict().Clone(); | 
 |   base::Value::List* debug_permissions_policy = | 
 |       debug_app.FindList("permissions_policy"); | 
 |   EXPECT_TRUE(debug_permissions_policy != nullptr); | 
 |   EXPECT_EQ(*debug_permissions_policy, expected_permissions_policy); | 
 | } | 
 | }  // namespace web_app |