| // Copyright 2016 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/ui/webui/settings/site_settings_handler.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/barrier_closure.h" |
| #include "base/check_deref.h" |
| #include "base/command_line.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/functional/callback.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/json/json_reader.h" |
| #include "base/json/values_util.h" |
| #include "base/logging.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/raw_ref.h" |
| #include "base/strings/string_piece.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/bind.h" |
| #include "base/test/gmock_callback_support.h" |
| #include "base/test/simple_test_clock.h" |
| #include "base/test/test_future.h" |
| #include "base/test/values_test_util.h" |
| #include "base/values.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.h" |
| #include "chrome/browser/apps/app_service/app_service_proxy.h" |
| #include "chrome/browser/apps/app_service/app_service_proxy_factory.h" |
| #include "chrome/browser/bluetooth/bluetooth_chooser_context_factory.h" |
| #include "chrome/browser/browsing_data/chrome_browsing_data_model_delegate.h" |
| #include "chrome/browser/browsing_topics/browsing_topics_service_factory.h" |
| #include "chrome/browser/content_settings/host_content_settings_map_factory.h" |
| #include "chrome/browser/engagement/site_engagement_service_factory.h" |
| #include "chrome/browser/extensions/extension_service.h" |
| #include "chrome/browser/extensions/test_extension_system.h" |
| #include "chrome/browser/file_system_access/chrome_file_system_access_permission_context.h" |
| #include "chrome/browser/file_system_access/file_system_access_permission_context_factory.h" |
| #include "chrome/browser/history/history_service_factory.h" |
| #include "chrome/browser/permissions/notification_permission_review_service_factory.h" |
| #include "chrome/browser/permissions/notifications_engagement_service_factory.h" |
| #include "chrome/browser/permissions/permission_decision_auto_blocker_factory.h" |
| #include "chrome/browser/prefs/browser_prefs.h" |
| #include "chrome/browser/privacy_sandbox/mock_privacy_sandbox_service.h" |
| #include "chrome/browser/privacy_sandbox/privacy_sandbox_service_factory.h" |
| #include "chrome/browser/ui/browser_window.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/browser/ui/webui/settings/site_settings_helper.h" |
| #include "chrome/browser/usb/usb_chooser_context.h" |
| #include "chrome/browser/usb/usb_chooser_context_factory.h" |
| #include "chrome/browser/web_applications/web_app_helpers.h" |
| #include "chrome/common/chrome_features.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "chrome/test/base/browser_with_test_window_test.h" |
| #include "chrome/test/base/testing_browser_process.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "chrome/test/base/testing_profile_manager.h" |
| #include "components/browsing_data/content/browsing_data_model.h" |
| #include "components/browsing_data/content/fake_browsing_data_model.h" |
| #include "components/browsing_data/content/mock_cookie_helper.h" |
| #include "components/browsing_data/content/mock_local_storage_helper.h" |
| #include "components/browsing_topics/browsing_topics_service.h" |
| #include "components/browsing_topics/test_util.h" |
| #include "components/client_hints/common/client_hints.h" |
| #include "components/content_settings/core/browser/cookie_settings.h" |
| #include "components/content_settings/core/browser/host_content_settings_map.h" |
| #include "components/content_settings/core/common/content_settings.h" |
| #include "components/content_settings/core/common/content_settings_pattern.h" |
| #include "components/content_settings/core/common/content_settings_types.h" |
| #include "components/content_settings/core/common/pref_names.h" |
| #include "components/history/core/browser/history_service.h" |
| #include "components/infobars/content/content_infobar_manager.h" |
| #include "components/infobars/core/infobar.h" |
| #include "components/permissions/contexts/bluetooth_chooser_context.h" |
| #include "components/permissions/object_permission_context_base.h" |
| #include "components/permissions/permission_decision_auto_blocker.h" |
| #include "components/permissions/permission_uma_util.h" |
| #include "components/permissions/permission_util.h" |
| #include "components/permissions/test/object_permission_context_base_mock_permission_observer.h" |
| #include "components/permissions/test/permission_test_util.h" |
| #include "components/prefs/testing_pref_service.h" |
| #include "components/services/app_service/public/cpp/app_registry_cache.h" |
| #include "components/services/app_service/public/cpp/app_types.h" |
| #include "components/services/app_service/public/cpp/app_update.h" |
| #include "components/site_engagement/content/site_engagement_score.h" |
| #include "components/sync_preferences/testing_pref_service_syncable.h" |
| #include "components/ukm/test_ukm_recorder.h" |
| #include "content/public/browser/browsing_data_remover.h" |
| #include "content/public/browser/navigation_controller.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/browser/web_ui_data_source.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "content/public/test/test_web_ui.h" |
| #include "device/bluetooth/bluetooth_adapter_factory.h" |
| #include "device/bluetooth/test/mock_bluetooth_adapter.h" |
| #include "device/bluetooth/test/mock_bluetooth_device.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/common/extension_builder.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "ppapi/buildflags/buildflags.h" |
| #include "services/device/public/cpp/test/fake_usb_device_manager.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "third_party/blink/public/common/storage_key/storage_key.h" |
| #include "third_party/blink/public/mojom/bluetooth/web_bluetooth.mojom.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/text/bytes_formatting.h" |
| #include "ui/webui/webui_allowlist.h" |
| #include "url/gurl.h" |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| #include "chrome/browser/ash/login/users/fake_chrome_user_manager.h" |
| #include "components/account_id/account_id.h" |
| #include "components/user_manager/scoped_user_manager.h" |
| #endif |
| |
| #if BUILDFLAG(ENABLE_PLUGINS) |
| #include "chrome/browser/plugins/chrome_plugin_service_filter.h" |
| #endif |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| #include "chrome/browser/hid/hid_chooser_context.h" |
| #include "chrome/browser/hid/hid_chooser_context_factory.h" |
| #include "chrome/browser/serial/serial_chooser_context.h" |
| #include "chrome/browser/serial/serial_chooser_context_factory.h" |
| #include "chrome/browser/ui/web_applications/test/isolated_web_app_test_utils.h" |
| #include "chrome/browser/web_applications/test/web_app_install_test_utils.h" |
| #include "chrome/browser/web_applications/web_app_id.h" |
| #include "services/device/public/cpp/test/fake_hid_manager.h" |
| #include "services/device/public/cpp/test/fake_serial_port_manager.h" |
| #include "services/device/public/mojom/hid.mojom.h" |
| #include "services/device/public/mojom/serial.mojom.h" |
| #endif |
| |
| namespace { |
| |
| using ::base::test::ParseJson; |
| using ::base::test::RunClosure; |
| using ::base::test::TestFuture; |
| using ::testing::_; |
| using ::testing::NiceMock; |
| using ::testing::Return; |
| using ::testing::UnorderedElementsAre; |
| |
| constexpr char kCallbackId[] = "test-callback-id"; |
| constexpr char kSetting[] = "setting"; |
| constexpr char kSource[] = "source"; |
| constexpr char kExtensionName[] = "Test Extension"; |
| constexpr char kTestUserEmail[] = "user@example.com"; |
| |
| const struct PatternContentTypeTestCase { |
| struct { |
| const char* const pattern; |
| const char* const content_type; |
| } arguments; |
| struct { |
| const bool validity; |
| const char* const reason; |
| } expected; |
| } kPatternsAndContentTypeTestCases[]{ |
| {{"https://google.com", "cookies"}, {true, ""}}, |
| {{";", "cookies"}, {false, "Not a valid web address"}}, |
| {{"*", "cookies"}, {false, "Not a valid web address"}}, |
| {{"chrome://test", "popups"}, {false, "Not a valid web address"}}, |
| {{"chrome-untrusted://test", "popups"}, {false, "Not a valid web address"}}, |
| {{"devtools://devtools", "popups"}, {false, "Not a valid web address"}}, |
| {{"chrome-search://search", "popups"}, {false, "Not a valid web address"}}, |
| {{"http://google.com", "location"}, {false, "Origin must be secure"}}, |
| {{"http://127.0.0.1", "location"}, {true, ""}}, // Localhost is secure. |
| {{"http://[::1]", "location"}, {true, ""}}}; |
| |
| // Converts |etld_plus1| into an HTTPS SchemefulSite. |
| net::SchemefulSite ConvertEtldToSchemefulSite(const std::string etld_plus1) { |
| return net::SchemefulSite(GURL(std::string(url::kHttpsScheme) + |
| url::kStandardSchemeSeparator + etld_plus1 + |
| "/")); |
| } |
| |
| // Validates that the list of sites are aligned with the first party sets |
| // mapping. |
| void ValidateSitesWithFps( |
| const base::Value::List& storage_and_cookie_list, |
| base::flat_map<net::SchemefulSite, net::SchemefulSite>& first_party_sets) { |
| for (const base::Value& site_group_value : storage_and_cookie_list) { |
| const base::Value::Dict& site_group = site_group_value.GetDict(); |
| std::string etld_plus1 = *site_group.FindString("etldPlus1"); |
| auto schemeful_site = ConvertEtldToSchemefulSite(etld_plus1); |
| |
| if (first_party_sets.count(schemeful_site)) { |
| // Ensure that the `fpsOwner` is set correctly and aligned with |
| // |first_party_sets| mapping of site group owners. |
| std::string owner_etldplus1 = |
| first_party_sets[schemeful_site].GetURL().host(); |
| ASSERT_EQ(owner_etldplus1, *site_group.FindString("fpsOwner")); |
| if (owner_etldplus1 == "google.com") { |
| ASSERT_EQ(2, *site_group.FindInt("fpsNumMembers")); |
| ASSERT_EQ(false, *site_group.FindBool("fpsEnterpriseManaged")); |
| } else if (owner_etldplus1 == "example.com") { |
| ASSERT_EQ(1, *site_group.FindInt("fpsNumMembers")); |
| ASSERT_EQ(true, *site_group.FindBool("fpsEnterpriseManaged")); |
| } |
| } else { |
| // The site is not part of a FPS therefore doesn't have `fpsOwner` or |
| // `fpsNumMembers` set. `FindString` and `FindInt` should return null. |
| ASSERT_FALSE(site_group.FindString("fpsOwner")); |
| ASSERT_FALSE(site_group.FindInt("fpsNumMembers")); |
| ASSERT_FALSE(site_group.FindBool("fpsEnterpriseManaged")); |
| } |
| } |
| } |
| |
| apps::AppPtr MakeApp(const std::string& app_id, |
| apps::AppType app_type, |
| const std::string& publisher_id, |
| apps::Readiness readiness, |
| apps::InstallReason install_reason) { |
| apps::AppPtr app = std::make_unique<apps::App>(app_type, app_id); |
| app->publisher_id = publisher_id; |
| app->readiness = readiness; |
| app->install_reason = install_reason; |
| return app; |
| } |
| |
| void RegisterWebApp(Profile* profile, apps::AppPtr app) { |
| apps::AppRegistryCache& cache = |
| apps::AppServiceProxyFactory::GetForProfile(profile)->AppRegistryCache(); |
| std::vector<apps::AppPtr> deltas; |
| deltas.push_back(std::move(app)); |
| cache.OnApps(std::move(deltas), apps::AppType::kWeb, |
| /*should_notify_initialized=*/true); |
| } |
| |
| struct TestModels { |
| scoped_refptr<browsing_data::MockCookieHelper> cookie_helper; |
| scoped_refptr<browsing_data::MockLocalStorageHelper> local_storage_helper; |
| const raw_ref<FakeBrowsingDataModel, ExperimentalAsh> browsing_data_model; |
| }; |
| |
| } // namespace |
| |
| namespace settings { |
| |
| // Helper class for setting ContentSettings via different sources. |
| class ContentSettingSourceSetter { |
| public: |
| ContentSettingSourceSetter(TestingProfile* profile, |
| ContentSettingsType content_type) |
| : prefs_(profile->GetTestingPrefService()), content_type_(content_type) {} |
| ContentSettingSourceSetter(const ContentSettingSourceSetter&) = delete; |
| ContentSettingSourceSetter& operator=(const ContentSettingSourceSetter&) = |
| delete; |
| |
| void SetPolicyDefault(ContentSetting setting) { |
| prefs_->SetManagedPref(GetPrefNameForDefaultPermissionSetting(), |
| std::make_unique<base::Value>(setting)); |
| } |
| |
| const char* GetPrefNameForDefaultPermissionSetting() { |
| switch (content_type_) { |
| case ContentSettingsType::NOTIFICATIONS: |
| return prefs::kManagedDefaultNotificationsSetting; |
| default: |
| // Add support as needed. |
| NOTREACHED(); |
| return ""; |
| } |
| } |
| |
| private: |
| raw_ptr<sync_preferences::TestingPrefServiceSyncable> prefs_; |
| ContentSettingsType content_type_; |
| }; |
| |
| class SiteSettingsHandlerBaseTest : public testing::Test { |
| public: |
| SiteSettingsHandlerBaseTest() |
| : kNotifications(site_settings::ContentSettingsTypeToGroupName( |
| ContentSettingsType::NOTIFICATIONS)), |
| kCookies(site_settings::ContentSettingsTypeToGroupName( |
| ContentSettingsType::COOKIES)) { |
| // Fully initialize |profile_| in the constructor since some children |
| // classes need it right away for SetUp(). |
| testing_profile_manager_ = std::make_unique<TestingProfileManager>( |
| TestingBrowserProcess::GetGlobal()); |
| EXPECT_TRUE(testing_profile_manager_->SetUp()); |
| profile_ = testing_profile_manager_->CreateTestingProfile( |
| kTestUserEmail, |
| {{HistoryServiceFactory::GetInstance(), |
| HistoryServiceFactory::GetDefaultFactory()}}, |
| /*is_main_profile=*/true); |
| EXPECT_TRUE(profile_); |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| SetUpUserManager(profile_.get()); |
| #endif |
| } |
| |
| void SetUp() override { |
| browsing_topics::BrowsingTopicsServiceFactory::GetInstance() |
| ->SetTestingFactoryAndUse( |
| profile(), |
| base::BindLambdaForTesting([this](content::BrowserContext* context) |
| -> std::unique_ptr<KeyedService> { |
| auto mock_browsing_topics_service = std::make_unique< |
| browsing_topics::MockBrowsingTopicsService>(); |
| mock_browsing_topics_service_ = |
| mock_browsing_topics_service.get(); |
| return mock_browsing_topics_service; |
| })); |
| |
| mock_privacy_sandbox_service_ = static_cast<MockPrivacySandboxService*>( |
| PrivacySandboxServiceFactory::GetInstance()->SetTestingFactoryAndUse( |
| profile(), base::BindRepeating(&BuildMockPrivacySandboxService))); |
| |
| profile()->SetPermissionControllerDelegate( |
| permissions::GetPermissionControllerDelegate(profile())); |
| |
| handler_ = std::make_unique<SiteSettingsHandler>(profile()); |
| handler()->set_web_ui(web_ui()); |
| handler()->AllowJavascript(); |
| // AllowJavascript() adds a callback to create leveldb_env::ChromiumEnv |
| // which reads the FeatureList. Wait for the callback to be finished so that |
| // we won't destruct |feature_list_| before the callback is executed. |
| base::RunLoop().RunUntilIdle(); |
| web_ui()->ClearTrackedCalls(); |
| } |
| |
| void TearDown() override { |
| if (profile_) { |
| auto* partition = profile_->GetDefaultStoragePartition(); |
| if (partition) |
| partition->WaitForDeletionTasksForTesting(); |
| } |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| void SetUpUserManager(TestingProfile* profile) { |
| // On ChromeOS a user account is needed in order to check whether the user |
| // account is affiliated with the device owner for the purposes of applying |
| // enterprise policy. |
| constexpr char kTestUserGaiaId[] = "1111111111"; |
| auto fake_user_manager = std::make_unique<ash::FakeChromeUserManager>(); |
| auto* fake_user_manager_ptr = fake_user_manager.get(); |
| scoped_user_manager_ = std::make_unique<user_manager::ScopedUserManager>( |
| std::move(fake_user_manager)); |
| |
| auto account_id = |
| AccountId::FromUserEmailGaiaId(kTestUserEmail, kTestUserGaiaId); |
| fake_user_manager_ptr->AddUserWithAffiliation(account_id, |
| /*is_affiliated=*/true); |
| fake_user_manager_ptr->LoginUser(account_id); |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| void RecordNotification(permissions::NotificationsEngagementService* service, |
| GURL url, |
| int daily_average_count) { |
| // This many notifications were recorded during the past week in total. |
| int total_count = daily_average_count * 7; |
| service->RecordNotificationDisplayed(url, total_count); |
| } |
| |
| base::Time GetReferenceTime() { |
| base::Time time; |
| EXPECT_TRUE(base::Time::FromString("Sat, 1 Sep 2018 11:00:00 GMT", &time)); |
| return time; |
| } |
| |
| TestingProfile* profile() { return profile_.get(); } |
| Profile* incognito_profile() { return incognito_profile_; } |
| content::TestWebUI* web_ui() { return &web_ui_; } |
| SiteSettingsHandler* handler() { return handler_.get(); } |
| browsing_topics::MockBrowsingTopicsService* mock_browsing_topics_service() { |
| return mock_browsing_topics_service_; |
| } |
| MockPrivacySandboxService* mock_privacy_sandbox_service() { |
| return mock_privacy_sandbox_service_.get(); |
| } |
| |
| void ValidateBlockAutoplay(bool expected_value, bool expected_enabled) { |
| const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); |
| EXPECT_EQ("cr.webUIListenerCallback", data.function_name()); |
| |
| ASSERT_TRUE(data.arg1()->is_string()); |
| EXPECT_EQ("onBlockAutoplayStatusChanged", data.arg1()->GetString()); |
| |
| const base::Value* event_data = data.arg2(); |
| ASSERT_TRUE(event_data->is_dict()); |
| |
| absl::optional<bool> enabled = event_data->GetDict().FindBool("enabled"); |
| ASSERT_TRUE(enabled.has_value()); |
| EXPECT_EQ(expected_enabled, *enabled); |
| |
| const base::Value::Dict* pref_data = event_data->GetDict().FindDict("pref"); |
| ASSERT_TRUE(pref_data); |
| |
| absl::optional<bool> value = pref_data->FindBool("value"); |
| ASSERT_TRUE(value.has_value()); |
| EXPECT_EQ(expected_value, *value); |
| } |
| |
| void SetSoundContentSettingDefault(ContentSetting value) { |
| HostContentSettingsMap* content_settings = |
| HostContentSettingsMapFactory::GetForProfile(profile()); |
| content_settings->SetDefaultContentSetting(ContentSettingsType::SOUND, |
| value); |
| } |
| |
| void ValidateDefault(const ContentSetting expected_setting, |
| const site_settings::SiteSettingSource expected_source, |
| size_t expected_total_calls) { |
| EXPECT_EQ(expected_total_calls, web_ui()->call_data().size()); |
| |
| const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); |
| EXPECT_EQ("cr.webUIResponse", data.function_name()); |
| |
| ASSERT_TRUE(data.arg1()->is_string()); |
| EXPECT_EQ(kCallbackId, data.arg1()->GetString()); |
| |
| ASSERT_TRUE(data.arg2()->is_bool()); |
| ASSERT_TRUE(data.arg2()->GetBool()); |
| |
| const base::Value::Dict& default_value = data.arg3()->GetDict(); |
| const std::string* setting = default_value.FindString(kSetting); |
| ASSERT_TRUE(setting); |
| EXPECT_EQ(content_settings::ContentSettingToString(expected_setting), |
| *setting); |
| const std::string* source = default_value.FindString(kSource); |
| if (source) { |
| EXPECT_EQ(site_settings::SiteSettingSourceToString(expected_source), |
| *source); |
| } |
| } |
| |
| void ValidateOrigin(const std::string& expected_origin, |
| const std::string& expected_embedding, |
| const std::string& expected_display_name, |
| const ContentSetting expected_setting, |
| const site_settings::SiteSettingSource expected_source, |
| size_t expected_total_calls) { |
| EXPECT_EQ(expected_total_calls, web_ui()->call_data().size()); |
| |
| const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); |
| EXPECT_EQ("cr.webUIResponse", data.function_name()); |
| |
| ASSERT_TRUE(data.arg1()->is_string()); |
| EXPECT_EQ(kCallbackId, data.arg1()->GetString()); |
| ASSERT_TRUE(data.arg2()->is_bool()); |
| ASSERT_TRUE(data.arg2()->GetBool()); |
| |
| ASSERT_TRUE(data.arg3()->is_list()); |
| EXPECT_EQ(1U, data.arg3()->GetList().size()); |
| |
| const base::Value::Dict& exception = data.arg3()->GetList()[0].GetDict(); |
| |
| const std::string* origin = exception.FindString(site_settings::kOrigin); |
| ASSERT_TRUE(origin); |
| ASSERT_EQ(expected_origin, *origin); |
| |
| const std::string* display_name = |
| exception.FindString(site_settings::kDisplayName); |
| ASSERT_TRUE(display_name); |
| ASSERT_EQ(expected_display_name, *display_name); |
| |
| const std::string* embedding_origin = |
| exception.FindString(site_settings::kEmbeddingOrigin); |
| ASSERT_TRUE(embedding_origin); |
| ASSERT_EQ(expected_embedding, *embedding_origin); |
| |
| const std::string* setting = exception.FindString(site_settings::kSetting); |
| ASSERT_TRUE(setting); |
| ASSERT_EQ(content_settings::ContentSettingToString(expected_setting), |
| *setting); |
| |
| const std::string* source = exception.FindString(site_settings::kSource); |
| ASSERT_TRUE(source); |
| ASSERT_EQ(site_settings::SiteSettingSourceToString(expected_source), |
| *source); |
| } |
| |
| void ValidateNoOrigin(size_t expected_total_calls) { |
| EXPECT_EQ(expected_total_calls, web_ui()->call_data().size()); |
| |
| const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); |
| EXPECT_EQ("cr.webUIResponse", data.function_name()); |
| |
| ASSERT_TRUE(data.arg1()->is_string()); |
| EXPECT_EQ(kCallbackId, data.arg1()->GetString()); |
| |
| ASSERT_TRUE(data.arg2()->is_bool()); |
| ASSERT_TRUE(data.arg2()->GetBool()); |
| |
| const base::Value& exceptions = *data.arg3(); |
| ASSERT_TRUE(exceptions.is_list()); |
| EXPECT_TRUE(exceptions.GetList().empty()); |
| } |
| |
| void ValidatePattern(bool expected_validity, |
| size_t expected_total_calls, |
| std::string expected_reason) { |
| EXPECT_EQ(expected_total_calls, web_ui()->call_data().size()); |
| |
| const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); |
| EXPECT_EQ("cr.webUIResponse", data.function_name()); |
| |
| ASSERT_TRUE(data.arg1()->is_string()); |
| EXPECT_EQ(kCallbackId, data.arg1()->GetString()); |
| |
| ASSERT_TRUE(data.arg2()->is_bool()); |
| ASSERT_TRUE(data.arg2()->GetBool()); |
| |
| const base::Value* result = data.arg3(); |
| ASSERT_TRUE(result->is_dict()); |
| |
| absl::optional<bool> valid = result->GetDict().FindBool("isValid"); |
| ASSERT_TRUE(valid.has_value()); |
| EXPECT_EQ(expected_validity, *valid); |
| |
| const std::string* reason = result->GetDict().FindString("reason"); |
| ASSERT_TRUE(reason); |
| EXPECT_EQ(expected_reason, *reason); |
| } |
| |
| void ValidateIncognitoExists(bool expected_incognito, |
| size_t expected_total_calls) { |
| EXPECT_EQ(expected_total_calls, web_ui()->call_data().size()); |
| |
| const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); |
| EXPECT_EQ("cr.webUIListenerCallback", data.function_name()); |
| |
| ASSERT_TRUE(data.arg1()->is_string()); |
| EXPECT_EQ("onIncognitoStatusChanged", data.arg1()->GetString()); |
| |
| ASSERT_TRUE(data.arg2()->is_bool()); |
| EXPECT_EQ(expected_incognito, data.arg2()->GetBool()); |
| } |
| |
| void ValidateZoom(const std::string& expected_host, |
| const std::string& expected_zoom, |
| size_t expected_total_calls) { |
| EXPECT_EQ(expected_total_calls, web_ui()->call_data().size()); |
| |
| const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); |
| EXPECT_EQ("cr.webUIListenerCallback", data.function_name()); |
| |
| ASSERT_TRUE(data.arg1()->is_string()); |
| EXPECT_EQ("onZoomLevelsChanged", data.arg1()->GetString()); |
| |
| ASSERT_TRUE(data.arg2()->is_list()); |
| const base::Value::List& exceptions = data.arg2()->GetList(); |
| if (expected_host.empty()) { |
| EXPECT_EQ(0U, exceptions.size()); |
| } else { |
| EXPECT_EQ(1U, exceptions.size()); |
| |
| const base::Value::Dict& exception = exceptions[0].GetDict(); |
| |
| const std::string* host = exception.FindString("origin"); |
| ASSERT_TRUE(host); |
| ASSERT_EQ(expected_host, *host); |
| |
| const std::string* zoom = exception.FindString("zoom"); |
| ASSERT_TRUE(zoom); |
| ASSERT_EQ(expected_zoom, *zoom); |
| } |
| } |
| |
| void ValidateCookieSettingUpdate(const std::string expected_string, |
| const int expected_call_index) { |
| const content::TestWebUI::CallData& data = |
| *web_ui()->call_data()[expected_call_index]; |
| |
| ASSERT_EQ("cr.webUIListenerCallback", data.function_name()); |
| ASSERT_EQ("cookieSettingDescriptionChanged", data.arg1()->GetString()); |
| ASSERT_EQ(expected_string, data.arg2()->GetString()); |
| } |
| |
| void ValidateUsageInfo(const std::string& expected_usage_host, |
| const std::string& expected_usage_string, |
| const std::string& expected_cookie_string, |
| const std::string& expected_fps_member_count_string, |
| const bool expected_fps_policy) { |
| const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); |
| EXPECT_EQ("cr.webUIListenerCallback", data.function_name()); |
| |
| ASSERT_TRUE(data.arg_nth(0)->is_string()); |
| EXPECT_EQ("usage-total-changed", data.arg_nth(0)->GetString()); |
| |
| ASSERT_TRUE(data.arg_nth(1)->is_string()); |
| EXPECT_EQ(expected_usage_host, data.arg_nth(1)->GetString()); |
| |
| ASSERT_TRUE(data.arg_nth(2)->is_string()); |
| EXPECT_EQ(expected_usage_string, data.arg_nth(2)->GetString()); |
| |
| ASSERT_TRUE(data.arg_nth(3)->is_string()); |
| EXPECT_EQ(expected_cookie_string, data.arg_nth(3)->GetString()); |
| |
| ASSERT_TRUE(data.arg_nth(4)->is_string()); |
| EXPECT_EQ(expected_fps_member_count_string, data.arg_nth(4)->GetString()); |
| |
| ASSERT_TRUE(data.arg_nth(5)->is_bool()); |
| EXPECT_EQ(expected_fps_policy, data.arg_nth(5)->GetBool()); |
| } |
| |
| void ValidateNotificationPermissionUpdate() { |
| const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); |
| EXPECT_EQ("cr.webUIListenerCallback", data.function_name()); |
| |
| ASSERT_TRUE(data.arg1()->is_string()); |
| EXPECT_EQ("notification-permission-review-list-maybe-changed", |
| data.arg1()->GetString()); |
| |
| ASSERT_TRUE(data.arg2()->is_list()); |
| } |
| |
| void CreateIncognitoProfile() { |
| incognito_profile_ = profile_->GetOffTheRecordProfile( |
| Profile::OTRProfileID::PrimaryID(), /*create_if_needed=*/true); |
| } |
| |
| virtual void DestroyIncognitoProfile() { |
| if (incognito_profile_) { |
| profile_->DestroyOffTheRecordProfile(incognito_profile_); |
| incognito_profile_ = nullptr; |
| } |
| } |
| |
| void SetupModels(base::OnceCallback<void(const TestModels& models)> setup) { |
| scoped_refptr<browsing_data::MockCookieHelper> |
| mock_browsing_data_cookie_helper; |
| scoped_refptr<browsing_data::MockLocalStorageHelper> |
| mock_browsing_data_local_storage_helper; |
| |
| auto* storage_partition = profile()->GetDefaultStoragePartition(); |
| mock_browsing_data_cookie_helper = |
| new browsing_data::MockCookieHelper(storage_partition); |
| mock_browsing_data_local_storage_helper = |
| new browsing_data::MockLocalStorageHelper(storage_partition); |
| |
| auto container = std::make_unique<LocalDataContainer>( |
| mock_browsing_data_cookie_helper, |
| /*database_helper=*/nullptr, mock_browsing_data_local_storage_helper, |
| /*session_storage_helper=*/nullptr, |
| /*indexed_db_helper=*/nullptr, |
| /*file_system_helper=*/nullptr, |
| /*quota_helper=*/nullptr, |
| /*service_worker_helper=*/nullptr, |
| /*data_shared_worker_helper=*/nullptr, |
| /*cache_storage_helper=*/nullptr); |
| auto mock_cookies_tree_model = std::make_unique<CookiesTreeModel>( |
| std::move(container), profile()->GetExtensionSpecialStoragePolicy()); |
| |
| auto fake_browsing_data_model = std::make_unique<FakeBrowsingDataModel>(); |
| |
| std::move(setup).Run( |
| {mock_browsing_data_cookie_helper, |
| mock_browsing_data_local_storage_helper, |
| ToRawRef<ExperimentalAsh>(*fake_browsing_data_model)}); |
| |
| mock_browsing_data_local_storage_helper->Notify(); |
| mock_browsing_data_cookie_helper->Notify(); |
| |
| handler()->SetModelsForTesting(std::move(mock_cookies_tree_model), |
| std::move(fake_browsing_data_model)); |
| } |
| |
| // TODO(https://crbug.com/835712): Currently only set up the cookies and local |
| // storage nodes, will update all other nodes in the future. |
| void SetupModels() { |
| SetupModels(base::BindLambdaForTesting([](const TestModels& models) { |
| models.local_storage_helper->AddLocalStorageForStorageKey( |
| blink::StorageKey::CreateFromStringForTesting( |
| "https://www.example.com/"), |
| 2); |
| |
| models.cookie_helper->AddCookieSamples(GURL("http://example.com"), "A=1"); |
| models.cookie_helper->AddCookieSamples(GURL("https://www.example.com/"), |
| "B=1"); |
| models.cookie_helper->AddCookieSamples(GURL("http://abc.example.com"), |
| "C=1"); |
| models.cookie_helper->AddCookieSamples(GURL("http://google.com"), "A=1"); |
| models.cookie_helper->AddCookieSamples(GURL("http://google.com"), "B=1"); |
| models.cookie_helper->AddCookieSamples(GURL("http://google.com.au"), |
| "A=1"); |
| |
| models.cookie_helper->AddCookieSamples( |
| GURL("https://www.example.com"), |
| "__Host-A=1; Path=/; Partitioned; Secure;", |
| net::CookiePartitionKey::FromURLForTesting( |
| GURL("https://google.com.au"))); |
| models.cookie_helper->AddCookieSamples( |
| GURL("https://google.com.au"), |
| "__Host-A=1; Path=/; Partitioned; Secure;", |
| net::CookiePartitionKey::FromURLForTesting( |
| GURL("https://google.com.au"))); |
| models.cookie_helper->AddCookieSamples( |
| GURL("https://www.another-example.com"), |
| "__Host-A=1; Path=/; Partitioned; Secure;", |
| net::CookiePartitionKey::FromURLForTesting( |
| GURL("https://google.com.au"))); |
| models.cookie_helper->AddCookieSamples( |
| GURL("https://www.example.com"), |
| "__Host-A=1; Path=/; Partitioned; Secure;", |
| net::CookiePartitionKey::FromURLForTesting( |
| GURL("https://google.com"))); |
| |
| // Add an entry which will not be grouped with any other entries. This |
| // will require a placeholder origin to be correctly added & removed. |
| models.cookie_helper->AddCookieSamples(GURL("http://ungrouped.com"), |
| "A=1"); |
| |
| models.browsing_data_model->AddBrowsingData( |
| url::Origin::Create(GURL("https://www.google.com")), |
| BrowsingDataModel::StorageType::kTrustTokens, 50000000000); |
| })); |
| } |
| |
| void SetupModelsWithIsolatedWebAppData( |
| const std::string& isolated_web_app_url, |
| int64_t usage) { |
| SetupModels(base::BindLambdaForTesting([&](const TestModels& models) { |
| models.browsing_data_model->AddBrowsingData( |
| url::Origin::Create(GURL(isolated_web_app_url)), |
| static_cast<BrowsingDataModel::StorageType>( |
| ChromeBrowsingDataModelDelegate::StorageType::kIsolatedWebApp), |
| usage); |
| })); |
| } |
| |
| base::Value::List GetOnStorageFetchedSentList() { |
| handler()->ClearAllSitesMapForTesting(); |
| |
| auto get_all_sites_args = base::Value::List().Append(kCallbackId); |
| handler()->HandleGetAllSites(get_all_sites_args); |
| handler()->ServicePendingRequests(); |
| |
| const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); |
| return data.arg2()->GetList().Clone(); |
| } |
| |
| std::vector<CookieTreeNode*> GetHostNodes(GURL url) { |
| std::vector<CookieTreeNode*> nodes; |
| for (const auto& host_node : |
| handler()->cookies_tree_model_->GetRoot()->children()) { |
| if (host_node->GetDetailedInfo().origin.GetURL() == url) { |
| nodes.push_back(host_node.get()); |
| } |
| } |
| return nodes; |
| } |
| |
| void SetupDefaultFirstPartySets(MockPrivacySandboxService* mock_service) { |
| EXPECT_CALL(*mock_service, GetFirstPartySetOwner(_)) |
| .WillRepeatedly( |
| [&](const GURL& url) -> absl::optional<net::SchemefulSite> { |
| auto first_party_sets = GetTestFirstPartySets(); |
| if (first_party_sets.count(net::SchemefulSite(url))) { |
| return first_party_sets[net::SchemefulSite(url)]; |
| } |
| |
| return absl::nullopt; |
| }); |
| } |
| |
| base::flat_map<net::SchemefulSite, net::SchemefulSite> |
| GetTestFirstPartySets() { |
| base::flat_map<net::SchemefulSite, net::SchemefulSite> first_party_sets = { |
| {ConvertEtldToSchemefulSite("google.com"), |
| ConvertEtldToSchemefulSite("google.com")}, |
| {ConvertEtldToSchemefulSite("google.com.au"), |
| ConvertEtldToSchemefulSite("google.com")}, |
| {ConvertEtldToSchemefulSite("example.com"), |
| ConvertEtldToSchemefulSite("example.com")}, |
| {ConvertEtldToSchemefulSite("unrelated.com"), |
| ConvertEtldToSchemefulSite("unrelated.com")}}; |
| |
| return first_party_sets; |
| } |
| |
| base::Value::List GetOriginList(int size) { |
| base::Value::List origins; |
| for (int i = 0; i < size; i++) { |
| origins.Append("https://example" + base::NumberToString(i) + ".org:443"); |
| } |
| return origins; |
| } |
| |
| scoped_refptr<const extensions::Extension> LoadExtension( |
| const std::string& extension_name) { |
| auto extension = extensions::ExtensionBuilder() |
| .SetManifest(base::Value::Dict() |
| .Set("name", kExtensionName) |
| .Set("version", "1.0.0") |
| .Set("manifest_version", 3)) |
| .Build(); |
| |
| extensions::TestExtensionSystem* extension_system = |
| static_cast<extensions::TestExtensionSystem*>( |
| extensions::ExtensionSystem::Get(profile())); |
| extensions::ExtensionService* extension_service = |
| extension_system->CreateExtensionService( |
| base::CommandLine::ForCurrentProcess(), base::FilePath(), |
| /*autoupdate_enabled=*/false); |
| extension_service->AddExtension(extension.get()); |
| return extension; |
| } |
| |
| void UnloadExtension(std::string extension_id) { |
| auto* extension_service = |
| extensions::ExtensionSystem::Get(profile())->extension_service(); |
| ASSERT_TRUE(extension_service); |
| extension_service->UnloadExtension( |
| extension_id, extensions::UnloadedExtensionReason::DISABLE); |
| } |
| |
| void ValidateCallbacksForNotificationPermission(int index) { |
| // When a notification permission is set or reset, there are two consecutive |
| // callbacks. The first one is to notify content setting observers, and |
| // the second one is to update safety check notification permission review. |
| ASSERT_EQ("contentSettingSitePermissionChanged", |
| web_ui()->call_data()[index]->arg1()->GetString()); |
| ASSERT_EQ("notification-permission-review-list-maybe-changed", |
| web_ui()->call_data()[index + 1]->arg1()->GetString()); |
| } |
| |
| // Content setting group name for the relevant ContentSettingsType. |
| const std::string kNotifications; |
| const std::string kCookies; |
| |
| const ContentSettingsType kPermissionNotifications = |
| ContentSettingsType::NOTIFICATIONS; |
| |
| // The number of listeners that are expected to fire when any content setting |
| // is changed. |
| const size_t kNumberContentSettingListeners = 2; |
| |
| private: |
| content::BrowserTaskEnvironment task_environment_; |
| std::unique_ptr<TestingProfileManager> testing_profile_manager_; |
| raw_ptr<TestingProfile> profile_ = nullptr; |
| raw_ptr<Profile> incognito_profile_ = nullptr; |
| content::TestWebUI web_ui_; |
| std::unique_ptr<SiteSettingsHandler> handler_; |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_; |
| #endif |
| raw_ptr<browsing_topics::MockBrowsingTopicsService> |
| mock_browsing_topics_service_; |
| raw_ptr<MockPrivacySandboxService> mock_privacy_sandbox_service_; |
| }; |
| |
| class SiteSettingsHandlerTest : public SiteSettingsHandlerBaseTest, |
| public testing::WithParamInterface<bool> {}; |
| |
| // True if testing for handle clear unpartitioned usage with HTTPS scheme URL. |
| // When set to true, the tests use HTTPS scheme as origin. When set to |
| // false, the tests use HTTP scheme as origin. |
| INSTANTIATE_TEST_SUITE_P(All, SiteSettingsHandlerTest, testing::Bool()); |
| |
| TEST_F(SiteSettingsHandlerTest, GetAndSetDefault) { |
| // Test the JS -> C++ -> JS callback path for getting and setting defaults. |
| base::Value::List get_args; |
| get_args.Append(kCallbackId); |
| get_args.Append(kNotifications); |
| handler()->HandleGetDefaultValueForContentType(get_args); |
| ValidateDefault(CONTENT_SETTING_ASK, |
| site_settings::SiteSettingSource::kDefault, 1U); |
| |
| // Set the default to 'Blocked'. |
| base::Value::List set_args; |
| set_args.Append(kNotifications); |
| set_args.Append( |
| content_settings::ContentSettingToString(CONTENT_SETTING_BLOCK)); |
| handler()->HandleSetDefaultValueForContentType(set_args); |
| |
| EXPECT_EQ(2U, web_ui()->call_data().size()); |
| |
| // Verify that the default has been set to 'Blocked'. |
| handler()->HandleGetDefaultValueForContentType(get_args); |
| ValidateDefault(CONTENT_SETTING_BLOCK, |
| site_settings::SiteSettingSource::kDefault, 3U); |
| } |
| |
| // Flaky on CrOS and Linux. https://crbug.com/930481 |
| TEST_F(SiteSettingsHandlerTest, GetAllSites) { |
| base::Value::List get_all_sites_args; |
| get_all_sites_args.Append(kCallbackId); |
| |
| // Test all sites is empty when there are no preferences. |
| handler()->HandleGetAllSites(get_all_sites_args); |
| EXPECT_EQ(1U, web_ui()->call_data().size()); |
| |
| { |
| const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); |
| EXPECT_EQ("cr.webUIResponse", data.function_name()); |
| EXPECT_EQ(kCallbackId, data.arg1()->GetString()); |
| ASSERT_TRUE(data.arg2()->GetBool()); |
| |
| const base::Value::List& site_groups = data.arg3()->GetList(); |
| EXPECT_TRUE(site_groups.empty()); |
| } |
| |
| // Add a couple of exceptions and check they appear in all sites. |
| HostContentSettingsMap* map = |
| HostContentSettingsMapFactory::GetForProfile(profile()); |
| const GURL url1("http://example.com"); |
| const GURL url2("https://other.example.com"); |
| map->SetContentSettingDefaultScope( |
| url1, url1, ContentSettingsType::NOTIFICATIONS, CONTENT_SETTING_BLOCK); |
| map->SetContentSettingDefaultScope( |
| url2, url2, ContentSettingsType::NOTIFICATIONS, CONTENT_SETTING_ALLOW); |
| handler()->HandleGetAllSites(get_all_sites_args); |
| |
| { |
| const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); |
| EXPECT_EQ("cr.webUIResponse", data.function_name()); |
| EXPECT_EQ(kCallbackId, data.arg1()->GetString()); |
| ASSERT_TRUE(data.arg2()->GetBool()); |
| |
| const base::Value::List& site_groups = data.arg3()->GetList(); |
| EXPECT_EQ(1UL, site_groups.size()); |
| for (const base::Value& site_group_val : site_groups) { |
| const base::Value::Dict& site_group = site_group_val.GetDict(); |
| const std::string& etld_plus1_string = |
| CHECK_DEREF(site_group.FindString("etldPlus1")); |
| const base::Value::List& origin_list = |
| CHECK_DEREF(site_group.FindList("origins")); |
| EXPECT_EQ("example.com", etld_plus1_string); |
| EXPECT_EQ(2UL, origin_list.size()); |
| const base::Value::Dict& first_origin = origin_list[0].GetDict(); |
| const base::Value::Dict& second_origin = origin_list[1].GetDict(); |
| EXPECT_EQ(url1.spec(), CHECK_DEREF(first_origin.FindString("origin"))); |
| EXPECT_EQ(0, first_origin.FindDouble("engagement")); |
| EXPECT_EQ(url2.spec(), CHECK_DEREF(second_origin.FindString("origin"))); |
| EXPECT_EQ(0, second_origin.FindDouble("engagement")); |
| } |
| } |
| |
| // Add an additional exception belonging to a different eTLD+1. |
| const GURL url3("https://example2.net"); |
| map->SetContentSettingDefaultScope( |
| url3, url3, ContentSettingsType::NOTIFICATIONS, CONTENT_SETTING_BLOCK); |
| handler()->HandleGetAllSites(get_all_sites_args); |
| |
| { |
| const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); |
| EXPECT_EQ("cr.webUIResponse", data.function_name()); |
| |
| EXPECT_EQ(kCallbackId, data.arg1()->GetString()); |
| ASSERT_TRUE(data.arg2()->GetBool()); |
| |
| const base::Value::List& site_groups = data.arg3()->GetList(); |
| EXPECT_EQ(2UL, site_groups.size()); |
| for (const base::Value& site_group_val : site_groups) { |
| const base::Value::Dict& site_group = site_group_val.GetDict(); |
| const std::string& etld_plus1_string = |
| CHECK_DEREF(site_group.FindString("etldPlus1")); |
| const base::Value::List& origin_list = |
| CHECK_DEREF(site_group.FindList("origins")); |
| if (etld_plus1_string == "example2.net") { |
| EXPECT_EQ(1UL, origin_list.size()); |
| const base::Value::Dict& first_origin = origin_list[0].GetDict(); |
| EXPECT_EQ(url3.spec(), CHECK_DEREF(first_origin.FindString("origin"))); |
| } else { |
| EXPECT_EQ("example.com", etld_plus1_string); |
| } |
| } |
| } |
| |
| // Test embargoed settings also appear. |
| permissions::PermissionDecisionAutoBlocker* auto_blocker = |
| PermissionDecisionAutoBlockerFactory::GetForProfile(profile()); |
| base::SimpleTestClock clock; |
| clock.SetNow(base::Time::Now()); |
| auto_blocker->SetClockForTesting(&clock); |
| const GURL url4("https://example2.co.uk"); |
| for (int i = 0; i < 3; ++i) { |
| auto_blocker->RecordDismissAndEmbargo( |
| url4, ContentSettingsType::NOTIFICATIONS, false); |
| } |
| EXPECT_EQ( |
| CONTENT_SETTING_BLOCK, |
| auto_blocker->GetEmbargoResult(url4, ContentSettingsType::NOTIFICATIONS) |
| ->content_setting); |
| handler()->HandleGetAllSites(get_all_sites_args); |
| |
| { |
| const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); |
| EXPECT_EQ("cr.webUIResponse", data.function_name()); |
| EXPECT_EQ(kCallbackId, data.arg1()->GetString()); |
| ASSERT_TRUE(data.arg2()->GetBool()); |
| |
| EXPECT_EQ(3UL, data.arg3()->GetList().size()); |
| } |
| |
| // Check |url4| disappears from the list when its embargo expires. |
| clock.Advance(base::Days(8)); |
| handler()->HandleGetAllSites(get_all_sites_args); |
| |
| { |
| const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); |
| EXPECT_EQ("cr.webUIResponse", data.function_name()); |
| EXPECT_EQ(kCallbackId, data.arg1()->GetString()); |
| ASSERT_TRUE(data.arg2()->GetBool()); |
| |
| const base::Value::List& site_groups = data.arg3()->GetList(); |
| EXPECT_EQ(2UL, site_groups.size()); |
| EXPECT_EQ("example.com", |
| CHECK_DEREF(site_groups[0].GetDict().FindString("etldPlus1"))); |
| EXPECT_EQ("example2.net", |
| CHECK_DEREF(site_groups[1].GetDict().FindString("etldPlus1"))); |
| } |
| |
| // Add an expired embargo setting to an existing eTLD+1 group and make sure it |
| // still appears. |
| for (int i = 0; i < 3; ++i) { |
| auto_blocker->RecordDismissAndEmbargo( |
| url3, ContentSettingsType::NOTIFICATIONS, false); |
| } |
| EXPECT_EQ( |
| CONTENT_SETTING_BLOCK, |
| auto_blocker->GetEmbargoResult(url3, ContentSettingsType::NOTIFICATIONS) |
| ->content_setting); |
| clock.Advance(base::Days(8)); |
| EXPECT_FALSE( |
| auto_blocker->GetEmbargoResult(url3, ContentSettingsType::NOTIFICATIONS) |
| .has_value()); |
| |
| handler()->HandleGetAllSites(get_all_sites_args); |
| { |
| const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); |
| EXPECT_EQ("cr.webUIResponse", data.function_name()); |
| EXPECT_EQ(kCallbackId, data.arg1()->GetString()); |
| ASSERT_TRUE(data.arg2()->GetBool()); |
| |
| const base::Value::List& site_groups = data.arg3()->GetList(); |
| EXPECT_EQ(2UL, site_groups.size()); |
| EXPECT_EQ("example.com", |
| CHECK_DEREF(site_groups[0].GetDict().FindString("etldPlus1"))); |
| EXPECT_EQ("example2.net", |
| CHECK_DEREF(site_groups[1].GetDict().FindString("etldPlus1"))); |
| } |
| |
| // Add an expired embargo to a new eTLD+1 and make sure it doesn't appear. |
| const GURL url5("http://test.example5.com"); |
| for (int i = 0; i < 3; ++i) { |
| auto_blocker->RecordDismissAndEmbargo( |
| url5, ContentSettingsType::NOTIFICATIONS, false); |
| } |
| EXPECT_EQ( |
| CONTENT_SETTING_BLOCK, |
| auto_blocker->GetEmbargoResult(url5, ContentSettingsType::NOTIFICATIONS) |
| ->content_setting); |
| clock.Advance(base::Days(8)); |
| EXPECT_FALSE( |
| auto_blocker->GetEmbargoResult(url5, ContentSettingsType::NOTIFICATIONS) |
| .has_value()); |
| |
| handler()->HandleGetAllSites(get_all_sites_args); |
| { |
| const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); |
| EXPECT_EQ("cr.webUIResponse", data.function_name()); |
| EXPECT_EQ(kCallbackId, data.arg1()->GetString()); |
| ASSERT_TRUE(data.arg2()->GetBool()); |
| |
| const base::Value::List& site_groups = data.arg3()->GetList(); |
| EXPECT_EQ(2UL, site_groups.size()); |
| EXPECT_EQ("example.com", |
| CHECK_DEREF(site_groups[0].GetDict().FindString("etldPlus1"))); |
| EXPECT_EQ("example2.net", |
| CHECK_DEREF(site_groups[1].GetDict().FindString("etldPlus1"))); |
| } |
| |
| // Same extension url from different content setting types shows only one |
| // extension site group. |
| auto extension = LoadExtension(kExtensionName); |
| map->SetContentSettingDefaultScope(extension->url(), extension->url(), |
| ContentSettingsType::NOTIFICATIONS, |
| CONTENT_SETTING_BLOCK); |
| map->SetContentSettingDefaultScope(extension->url(), extension->url(), |
| ContentSettingsType::GEOLOCATION, |
| CONTENT_SETTING_BLOCK); |
| handler()->HandleGetAllSites(get_all_sites_args); |
| { |
| const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); |
| EXPECT_EQ("cr.webUIResponse", data.function_name()); |
| EXPECT_EQ(kCallbackId, data.arg1()->GetString()); |
| ASSERT_TRUE(data.arg2()->GetBool()); |
| |
| const base::Value::List& site_groups = data.arg3()->GetList(); |
| EXPECT_EQ(3UL, site_groups.size()); |
| // Extension etldPlus1 string will be in the pattern of |
| // "chrome-extension://<extension_id>" so it is before other site groups in |
| // the list. |
| EXPECT_EQ(extension->url().spec(), |
| CHECK_DEREF(site_groups[0].GetDict().FindString("etldPlus1"))); |
| EXPECT_EQ("example.com", |
| CHECK_DEREF(site_groups[1].GetDict().FindString("etldPlus1"))); |
| EXPECT_EQ("example2.net", |
| CHECK_DEREF(site_groups[2].GetDict().FindString("etldPlus1"))); |
| } |
| |
| // Each call to HandleGetAllSites() above added a callback to the profile's |
| // browsing_data::LocalStorageHelper, so make sure these aren't stuck waiting |
| // to run at the end of the test. |
| base::RunLoop run_loop; |
| run_loop.RunUntilIdle(); |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, Cookies) { |
| base::Value::List get_all_sites_args; |
| get_all_sites_args.Append(kCallbackId); |
| |
| // Tests that a cookie eTLD+1 origin, which should use a placeholder in |
| // AllSitesMap, returns the correct origin in GetAllSites. |
| // This corresponds to case 1 in InsertOriginIntoGroup. |
| { |
| SetupModels(base::BindLambdaForTesting([](const TestModels& models) { |
| models.cookie_helper->AddCookieSamples(GURL("http://c1.com"), "A=1"); |
| })); |
| |
| base::Value::List site_groups = GetOnStorageFetchedSentList(); |
| |
| ASSERT_EQ(1UL, site_groups.size()); |
| const base::Value::Dict& first_group = site_groups[0].GetDict(); |
| EXPECT_EQ("c1.com", CHECK_DEREF(first_group.FindString("etldPlus1"))); |
| EXPECT_EQ(1, *first_group.FindInt("numCookies")); |
| const base::Value::List& first_group_origins = |
| CHECK_DEREF(first_group.FindList("origins")); |
| ASSERT_EQ(1UL, first_group_origins.size()); |
| EXPECT_EQ( |
| "http://c1.com/", |
| CHECK_DEREF(first_group_origins[0].GetDict().FindString("origin"))); |
| EXPECT_EQ(1, first_group_origins[0].GetDict().FindInt("numCookies")); |
| } |
| |
| // Tests that multiple cookie eTLD+1 origins result in a single origin being |
| // returned in GetAllSites. |
| // This corresponds to case 2 in InsertOriginIntoGroup. |
| { |
| SetupModels(base::BindLambdaForTesting([](const TestModels& models) { |
| models.cookie_helper->AddCookieSamples(GURL("https://c2.com"), "A=1"); |
| models.cookie_helper->AddCookieSamples(GURL("https://c2.com"), "B=1"); |
| })); |
| |
| base::Value::List site_groups = GetOnStorageFetchedSentList(); |
| |
| ASSERT_EQ(1UL, site_groups.size()); |
| const base::Value::Dict& first_group = site_groups[0].GetDict(); |
| EXPECT_EQ("c2.com", CHECK_DEREF(first_group.FindString("etldPlus1"))); |
| EXPECT_EQ(2, *first_group.FindInt("numCookies")); |
| const base::Value::List& first_group_origins = |
| CHECK_DEREF(first_group.FindList("origins")); |
| ASSERT_EQ(1UL, first_group_origins.size()); |
| EXPECT_EQ( |
| "http://c2.com/", |
| CHECK_DEREF(first_group_origins[0].GetDict().FindString("origin"))); |
| EXPECT_EQ(2, first_group_origins[0].GetDict().FindInt("numCookies")); |
| } |
| |
| // Tests that an HTTP cookie origin will reuse an equivalent HTTPS origin if |
| // one exists. |
| // This corresponds to case 3 in InsertOriginIntoGroup. |
| { |
| SetupModels(base::BindLambdaForTesting([](const TestModels& models) { |
| models.browsing_data_model->AddBrowsingData( |
| url::Origin::Create(GURL("https://w.c3.com")), |
| BrowsingDataModel::StorageType::kTrustTokens, 50); |
| models.cookie_helper->AddCookieSamples(GURL("http://w.c3.com"), "A=1"); |
| })); |
| |
| base::Value::List site_groups = GetOnStorageFetchedSentList(); |
| |
| ASSERT_EQ(1UL, site_groups.size()); |
| const base::Value::Dict& first_group = site_groups[0].GetDict(); |
| EXPECT_EQ("c3.com", CHECK_DEREF(first_group.FindString("etldPlus1"))); |
| EXPECT_EQ(1, *first_group.FindInt("numCookies")); |
| const base::Value::List& first_group_origins = |
| CHECK_DEREF(first_group.FindList("origins")); |
| ASSERT_EQ(1UL, first_group_origins.size()); |
| EXPECT_EQ( |
| "https://w.c3.com/", |
| CHECK_DEREF(first_group_origins[0].GetDict().FindString("origin"))); |
| EXPECT_EQ(1, first_group_origins[0].GetDict().FindInt("numCookies")); |
| } |
| |
| // Tests that placeholder cookie eTLD+1 origins get removed from AllSitesMap |
| // when a more specific origin is added later. |
| { |
| SetupModels(base::BindLambdaForTesting([](const TestModels& models) { |
| models.cookie_helper->AddCookieSamples(GURL("https://c4.com"), "B=1"); |
| models.cookie_helper->AddCookieSamples(GURL("https://w.c4.com"), "A=1"); |
| })); |
| |
| base::Value::List site_groups = GetOnStorageFetchedSentList(); |
| |
| ASSERT_EQ(1UL, site_groups.size()); |
| const base::Value::Dict& first_group = site_groups[0].GetDict(); |
| EXPECT_EQ("c4.com", CHECK_DEREF(first_group.FindString("etldPlus1"))); |
| EXPECT_EQ(2, *first_group.FindInt("numCookies")); |
| const base::Value::List& first_group_origins = |
| CHECK_DEREF(first_group.FindList("origins")); |
| ASSERT_EQ(1UL, first_group_origins.size()); |
| EXPECT_EQ( |
| "https://w.c4.com/", |
| CHECK_DEREF(first_group_origins[0].GetDict().FindString("origin"))); |
| EXPECT_EQ(1, first_group_origins[0].GetDict().FindInt("numCookies")); |
| } |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, GetRecentSitePermissions) { |
| // Constants used only in this test. |
| std::string kAllowed = content_settings::ContentSettingToString( |
| ContentSetting::CONTENT_SETTING_ALLOW); |
| std::string kBlocked = content_settings::ContentSettingToString( |
| ContentSetting::CONTENT_SETTING_BLOCK); |
| std::string kEmbargo = |
| SiteSettingSourceToString(site_settings::SiteSettingSource::kEmbargo); |
| std::string kPreference = |
| SiteSettingSourceToString(site_settings::SiteSettingSource::kPreference); |
| |
| base::Value::List get_recent_permissions_args; |
| get_recent_permissions_args.Append(kCallbackId); |
| get_recent_permissions_args.Append(3); |
| |
| // Configure prefs and auto blocker with a controllable clock. |
| base::SimpleTestClock clock; |
| clock.SetNow(base::Time::Now()); |
| HostContentSettingsMap* map = |
| HostContentSettingsMapFactory::GetForProfile(profile()); |
| map->SetClockForTesting(&clock); |
| permissions::PermissionDecisionAutoBlocker* auto_blocker = |
| PermissionDecisionAutoBlockerFactory::GetForProfile(profile()); |
| auto_blocker->SetClockForTesting(&clock); |
| clock.Advance(base::Hours(1)); |
| |
| // Test recent permissions is empty when there are no preferences. |
| handler()->HandleGetRecentSitePermissions(get_recent_permissions_args); |
| EXPECT_EQ(1U, web_ui()->call_data().size()); |
| |
| { |
| const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); |
| EXPECT_EQ("cr.webUIResponse", data.function_name()); |
| EXPECT_EQ(kCallbackId, data.arg1()->GetString()); |
| ASSERT_TRUE(data.arg2()->GetBool()); |
| |
| const base::Value::List& recent_permissions = data.arg3()->GetList(); |
| EXPECT_TRUE(recent_permissions.empty()); |
| } |
| |
| // Add numerous permissions from different sources and confirm that the recent |
| // permissions are correctly transformed for usage by JS. |
| const GURL url1("https://example.com"); |
| const GURL url2("http://example.com"); |
| for (int i = 0; i < 3; ++i) |
| auto_blocker->RecordDismissAndEmbargo( |
| url1, ContentSettingsType::NOTIFICATIONS, false); |
| |
| clock.Advance(base::Hours(2)); |
| clock.Advance(base::Hours(1)); |
| CreateIncognitoProfile(); |
| HostContentSettingsMap* incognito_map = |
| HostContentSettingsMapFactory::GetForProfile(incognito_profile()); |
| incognito_map->SetClockForTesting(&clock); |
| |
| clock.Advance(base::Hours(1)); |
| permissions::PermissionDecisionAutoBlocker* incognito_auto_blocker = |
| PermissionDecisionAutoBlockerFactory::GetForProfile(incognito_profile()); |
| incognito_auto_blocker->SetClockForTesting(&clock); |
| for (int i = 0; i < 3; ++i) |
| incognito_auto_blocker->RecordDismissAndEmbargo( |
| url1, ContentSettingsType::NOTIFICATIONS, false); |
| |
| handler()->HandleGetRecentSitePermissions(get_recent_permissions_args); |
| { |
| const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); |
| EXPECT_EQ("cr.webUIResponse", data.function_name()); |
| EXPECT_EQ(kCallbackId, data.arg1()->GetString()); |
| ASSERT_TRUE(data.arg2()->GetBool()); |
| |
| const base::Value::List& recent_permissions = data.arg3()->GetList(); |
| EXPECT_EQ(2UL, recent_permissions.size()); |
| const base::Value::Dict& first_permission = recent_permissions[0].GetDict(); |
| const base::Value::Dict& second_permission = |
| recent_permissions[1].GetDict(); |
| |
| EXPECT_EQ(url1.spec(), CHECK_DEREF(second_permission.FindString("origin"))); |
| EXPECT_EQ(url1.spec(), CHECK_DEREF(first_permission.FindString("origin"))); |
| |
| EXPECT_TRUE(first_permission.FindBool("incognito").value_or(false)); |
| EXPECT_FALSE(second_permission.FindBool("incognito").value_or(true)); |
| |
| const base::Value::List& incognito_url1_permissions = |
| CHECK_DEREF(first_permission.FindList("recentPermissions")); |
| const base::Value::List& url1_permissions = |
| CHECK_DEREF(second_permission.FindList("recentPermissions")); |
| |
| EXPECT_EQ(1UL, incognito_url1_permissions.size()); |
| const base::Value::Dict& first_incognito_permission = |
| incognito_url1_permissions[0].GetDict(); |
| |
| EXPECT_EQ(kNotifications, |
| CHECK_DEREF(first_incognito_permission.FindString("type"))); |
| EXPECT_EQ(kBlocked, |
| CHECK_DEREF(first_incognito_permission.FindString("setting"))); |
| EXPECT_EQ(kEmbargo, |
| CHECK_DEREF(first_incognito_permission.FindString("source"))); |
| |
| const base::Value::Dict& first_url_permission = |
| url1_permissions[0].GetDict(); |
| EXPECT_EQ(kNotifications, |
| CHECK_DEREF(first_url_permission.FindString("type"))); |
| EXPECT_EQ(kBlocked, |
| CHECK_DEREF(first_url_permission.FindString("setting"))); |
| EXPECT_EQ(kEmbargo, CHECK_DEREF(first_url_permission.FindString("source"))); |
| } |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, OnStorageFetched) { |
| SetupModels(); |
| |
| handler()->ClearAllSitesMapForTesting(); |
| handler()->OnStorageFetched(); |
| |
| const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); |
| EXPECT_EQ("cr.webUIListenerCallback", data.function_name()); |
| |
| ASSERT_TRUE(data.arg1()->is_string()); |
| EXPECT_EQ("onStorageListFetched", data.arg1()->GetString()); |
| |
| ASSERT_TRUE(data.arg2()->is_list()); |
| const base::Value::List& storage_and_cookie_list = data.arg2()->GetList(); |
| EXPECT_EQ(4U, storage_and_cookie_list.size()); |
| |
| { |
| const base::Value& site_group_val = storage_and_cookie_list[0]; |
| ASSERT_TRUE(site_group_val.is_dict()); |
| const base::Value::Dict& site_group = site_group_val.GetDict(); |
| |
| ASSERT_TRUE(site_group.FindString("etldPlus1")); |
| ASSERT_EQ("example.com", *site_group.FindString("etldPlus1")); |
| |
| EXPECT_EQ(3, site_group.FindDouble("numCookies")); |
| |
| const base::Value::List* origin_list = site_group.FindList("origins"); |
| ASSERT_TRUE(origin_list); |
| // There will be 2 origins in this case. Cookie node with url |
| // http://www.example.com/ will be treat as https://www.example.com/ because |
| // this url existed in the storage nodes. |
| EXPECT_EQ(2U, origin_list->size()); |
| |
| const base::Value::Dict& origin_info_0 = (*origin_list)[0].GetDict(); |
| |
| EXPECT_EQ("http://abc.example.com/", |
| CHECK_DEREF(origin_info_0.FindString("origin"))); |
| EXPECT_EQ(0, origin_info_0.FindDouble("engagement")); |
| EXPECT_EQ(0, origin_info_0.FindDouble("usage")); |
| EXPECT_EQ(1, origin_info_0.FindDouble("numCookies")); |
| |
| const base::Value::Dict& origin_info_1 = (*origin_list)[1].GetDict(); |
| |
| // Even though in the cookies the scheme is http, it still stored as https |
| // because there is https data stored. |
| EXPECT_EQ("https://www.example.com/", |
| CHECK_DEREF(origin_info_1.FindString("origin"))); |
| EXPECT_EQ(0, origin_info_1.FindDouble("engagement")); |
| EXPECT_EQ(2, origin_info_1.FindDouble("usage")); |
| EXPECT_EQ(1, origin_info_1.FindDouble("numCookies")); |
| } |
| |
| { |
| const base::Value::Dict& site_group = storage_and_cookie_list[1].GetDict(); |
| |
| ASSERT_TRUE(site_group.FindString("etldPlus1")); |
| ASSERT_EQ("google.com", *site_group.FindString("etldPlus1")); |
| |
| EXPECT_EQ(3, site_group.FindDouble("numCookies")); |
| |
| const base::Value::List* origin_list = site_group.FindList("origins"); |
| ASSERT_TRUE(origin_list); |
| |
| EXPECT_EQ(2U, origin_list->size()); |
| |
| const base::Value::Dict& partitioned_origin_info = |
| (*origin_list)[0].GetDict(); |
| |
| EXPECT_EQ("https://www.example.com/", |
| CHECK_DEREF(partitioned_origin_info.FindString("origin"))); |
| EXPECT_EQ(0, partitioned_origin_info.FindDouble("engagement")); |
| EXPECT_EQ(0, partitioned_origin_info.FindDouble("usage")); |
| EXPECT_EQ(1, partitioned_origin_info.FindDouble("numCookies")); |
| EXPECT_TRUE( |
| partitioned_origin_info.FindBool("isPartitioned").value_or(false)); |
| |
| const base::Value::Dict& unpartitioned_origin_info = |
| (*origin_list)[1].GetDict(); |
| |
| EXPECT_EQ("https://www.google.com/", |
| CHECK_DEREF(unpartitioned_origin_info.FindString("origin"))); |
| EXPECT_EQ(0, unpartitioned_origin_info.FindDouble("engagement")); |
| EXPECT_EQ(50000000000, unpartitioned_origin_info.FindDouble("usage")); |
| EXPECT_EQ(0, unpartitioned_origin_info.FindDouble("numCookies")); |
| EXPECT_FALSE( |
| unpartitioned_origin_info.FindBool("isPartitioned").value_or(true)); |
| } |
| |
| { |
| const base::Value& site_group_val = storage_and_cookie_list[2]; |
| ASSERT_TRUE(site_group_val.is_dict()); |
| const base::Value::Dict& site_group = site_group_val.GetDict(); |
| |
| ASSERT_TRUE(site_group.FindString("etldPlus1")); |
| ASSERT_EQ("google.com.au", *site_group.FindString("etldPlus1")); |
| |
| EXPECT_EQ(4, site_group.FindDouble("numCookies")); |
| |
| const base::Value::List* origin_list = site_group.FindList("origins"); |
| ASSERT_TRUE(origin_list); |
| |
| // The unpartitioned cookie set for google.com.au should be associated with |
| // the eTLD+1, and thus won't have an origin entry as other origin entries |
| // exist for the unpartitioned storage. The partitioned cookie for |
| // google.com.au, partitioned by google.com.au should have also created an |
| // entry. |
| EXPECT_EQ(3U, origin_list->size()); |
| |
| const base::Value::Dict& partitioned_origin_one_info = |
| (*origin_list)[0].GetDict(); |
| |
| EXPECT_EQ("https://google.com.au/", |
| CHECK_DEREF(partitioned_origin_one_info.FindString("origin"))); |
| EXPECT_EQ(0, partitioned_origin_one_info.FindDouble("engagement")); |
| EXPECT_EQ(0, partitioned_origin_one_info.FindDouble("usage")); |
| EXPECT_EQ(1, partitioned_origin_one_info.FindDouble("numCookies")); |
| EXPECT_TRUE( |
| partitioned_origin_one_info.FindBool("isPartitioned").value_or(false)); |
| |
| const base::Value::Dict& partitioned_origin_two_info = |
| (*origin_list)[1].GetDict(); |
| EXPECT_EQ("https://www.another-example.com/", |
| CHECK_DEREF(partitioned_origin_two_info.FindString("origin"))); |
| EXPECT_EQ(0, partitioned_origin_two_info.FindDouble("engagement")); |
| EXPECT_EQ(0, partitioned_origin_two_info.FindDouble("usage")); |
| EXPECT_EQ(1, partitioned_origin_two_info.FindDouble("numCookies")); |
| EXPECT_TRUE( |
| partitioned_origin_two_info.FindBool("isPartitioned").value_or(false)); |
| |
| const base::Value::Dict& partitioned_origin_three_info = |
| (*origin_list)[2].GetDict(); |
| |
| EXPECT_EQ("https://www.example.com/", |
| CHECK_DEREF(partitioned_origin_three_info.FindString("origin"))); |
| EXPECT_EQ(0, partitioned_origin_three_info.FindDouble("engagement")); |
| EXPECT_EQ(0, partitioned_origin_three_info.FindDouble("usage")); |
| EXPECT_EQ(1, partitioned_origin_three_info.FindDouble("numCookies")); |
| EXPECT_TRUE(partitioned_origin_three_info.FindBool("isPartitioned") |
| .value_or(false)); |
| } |
| |
| { |
| const base::Value& site_group_val = storage_and_cookie_list[3]; |
| ASSERT_TRUE(site_group_val.is_dict()); |
| const base::Value::Dict& site_group = site_group_val.GetDict(); |
| |
| ASSERT_TRUE(site_group.FindString("etldPlus1")); |
| ASSERT_EQ("ungrouped.com", *site_group.FindString("etldPlus1")); |
| |
| EXPECT_EQ(1, site_group.FindDouble("numCookies")); |
| |
| const base::Value::List* origin_list = site_group.FindList("origins"); |
| ASSERT_TRUE(origin_list); |
| EXPECT_EQ(1U, origin_list->size()); |
| |
| const base::Value::Dict& origin_info = (*origin_list)[0].GetDict(); |
| |
| EXPECT_EQ("http://ungrouped.com/", |
| CHECK_DEREF(origin_info.FindString("origin"))); |
| EXPECT_EQ(0, origin_info.FindDouble("engagement")); |
| EXPECT_EQ(0, origin_info.FindDouble("usage")); |
| EXPECT_EQ(1, origin_info.FindDouble("numCookies")); |
| } |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, InstalledApps) { |
| GURL start_url("http://abc.example.com/path"); |
| RegisterWebApp( |
| profile(), |
| MakeApp(web_app::GenerateAppId(/*manifest_id=*/absl::nullopt, start_url), |
| apps::AppType::kWeb, start_url.spec(), apps::Readiness::kReady, |
| apps::InstallReason::kSync)); |
| |
| SetupModels(); |
| |
| base::Value::List storage_and_cookie_list = GetOnStorageFetchedSentList(); |
| EXPECT_EQ(4U, storage_and_cookie_list.size()); |
| |
| { |
| const base::Value& site_group_val = storage_and_cookie_list[0]; |
| ASSERT_TRUE(site_group_val.is_dict()); |
| const base::Value::Dict& site_group = site_group_val.GetDict(); |
| |
| ASSERT_TRUE(site_group.FindString("etldPlus1")); |
| ASSERT_EQ("example.com", *site_group.FindString("etldPlus1")); |
| |
| ASSERT_TRUE(site_group.FindBool("hasInstalledPWA").value_or(false)); |
| |
| const base::Value::List* origin_list = site_group.FindList("origins"); |
| ASSERT_TRUE(origin_list); |
| |
| const base::Value::Dict& origin_info = (*origin_list)[0].GetDict(); |
| |
| EXPECT_EQ("http://abc.example.com/", |
| CHECK_DEREF(origin_info.FindString("origin"))); |
| EXPECT_TRUE(origin_info.FindBool("isInstalled").value_or(false)); |
| } |
| |
| // Verify that installed booleans are false for other siteGroups/origins |
| { |
| const base::Value& site_group_val = storage_and_cookie_list[1]; |
| ASSERT_TRUE(site_group_val.is_dict()); |
| const base::Value::Dict& site_group = site_group_val.GetDict(); |
| |
| ASSERT_TRUE(site_group.FindString("etldPlus1")); |
| ASSERT_EQ("google.com", *site_group.FindString("etldPlus1")); |
| EXPECT_FALSE(site_group.FindBool("hasInstalledPWA").value_or(true)); |
| |
| const base::Value::List* origin_list = site_group.FindList("origins"); |
| ASSERT_TRUE(origin_list); |
| |
| for (const auto& origin_info : *origin_list) { |
| ASSERT_TRUE(origin_info.is_dict()); |
| EXPECT_FALSE( |
| origin_info.GetDict().FindBool("isInstalled").value_or(true)); |
| } |
| } |
| } |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| TEST_F(SiteSettingsHandlerTest, AllSitesDisplaysIsolatedWebAppName) { |
| std::string iwa_hostname = |
| "aerugqztij5biqquuk3mfwpsaibuegaqcitgfchwuosuofdjabzqaaic"; |
| GURL iwa_url("isolated-app://" + iwa_hostname); |
| GURL https_url("https://" + iwa_hostname); |
| |
| // Install an IWA at |iwa_url|. |
| web_app::test::AwaitStartWebAppProviderAndSubsystems(profile()); |
| web_app::AppId app_id = |
| web_app::AddDummyIsolatedAppToRegistry(profile(), iwa_url, "IWA Name"); |
| RegisterWebApp(profile(), |
| MakeApp(app_id, apps::AppType::kWeb, iwa_url.spec(), |
| apps::Readiness::kReady, apps::InstallReason::kUser)); |
| |
| SetupModels(base::DoNothing()); |
| HostContentSettingsMap* map = |
| HostContentSettingsMapFactory::GetForProfile(profile()); |
| map->SetContentSettingDefaultScope(iwa_url, iwa_url, |
| ContentSettingsType::NOTIFICATIONS, |
| CONTENT_SETTING_BLOCK); |
| map->SetContentSettingDefaultScope(https_url, https_url, |
| ContentSettingsType::NOTIFICATIONS, |
| CONTENT_SETTING_BLOCK); |
| |
| base::Value::List site_groups = GetOnStorageFetchedSentList(); |
| |
| ASSERT_EQ(site_groups.size(), 2u); |
| const base::Value::Dict& group1 = site_groups[0].GetDict(); |
| const base::Value::Dict& origin1 = |
| CHECK_DEREF(group1.FindList("origins"))[0].GetDict(); |
| EXPECT_EQ(CHECK_DEREF(group1.FindString("etldPlus1")), iwa_url); |
| EXPECT_EQ(CHECK_DEREF(group1.FindString("displayName")), "IWA Name"); |
| EXPECT_EQ(CHECK_DEREF(origin1.FindString("origin")), iwa_url); |
| |
| const base::Value::Dict& group2 = site_groups[1].GetDict(); |
| const base::Value::Dict& origin2 = |
| CHECK_DEREF(group2.FindList("origins"))[0].GetDict(); |
| EXPECT_EQ(CHECK_DEREF(group2.FindString("etldPlus1")), iwa_hostname); |
| EXPECT_EQ(CHECK_DEREF(group2.FindString("displayName")), iwa_hostname); |
| EXPECT_EQ(CHECK_DEREF(origin2.FindString("origin")), https_url); |
| } |
| #endif // !BUILDFLAG(IS_ANDROID) |
| |
| TEST_F(SiteSettingsHandlerTest, IncognitoExceptions) { |
| constexpr char kOriginToBlock[] = "https://www.blocked.com:443"; |
| |
| auto validate_exception = [&kOriginToBlock](const base::Value& exception) { |
| ASSERT_TRUE(exception.is_dict()); |
| |
| ASSERT_TRUE(exception.GetDict().FindString(site_settings::kOrigin)); |
| ASSERT_EQ(kOriginToBlock, |
| *exception.GetDict().FindString(site_settings::kOrigin)); |
| }; |
| |
| CreateIncognitoProfile(); |
| |
| { |
| base::Value::List set_args; |
| set_args.Append(kOriginToBlock); // Primary pattern. |
| set_args.Append(std::string()); // Secondary pattern. |
| set_args.Append(kNotifications); |
| set_args.Append( |
| content_settings::ContentSettingToString(CONTENT_SETTING_BLOCK)); |
| set_args.Append(true); // Incognito. |
| |
| handler()->HandleSetCategoryPermissionForPattern(set_args); |
| |
| base::Value::List get_exception_list_args; |
| get_exception_list_args.Append(kCallbackId); |
| get_exception_list_args.Append(kNotifications); |
| handler()->HandleGetExceptionList(get_exception_list_args); |
| |
| const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); |
| |
| ASSERT_TRUE(data.arg3()->is_list()); |
| const base::Value::List& exceptions = data.arg3()->GetList(); |
| ASSERT_EQ(1U, exceptions.size()); |
| |
| validate_exception(exceptions[0]); |
| } |
| |
| { |
| base::Value::List set_args; |
| set_args.Append(kOriginToBlock); // Primary pattern. |
| set_args.Append(std::string()); // Secondary pattern. |
| set_args.Append(kNotifications); |
| set_args.Append( |
| content_settings::ContentSettingToString(CONTENT_SETTING_BLOCK)); |
| set_args.Append(false); // Incognito. |
| |
| handler()->HandleSetCategoryPermissionForPattern(set_args); |
| |
| base::Value::List get_exception_list_args; |
| get_exception_list_args.Append(kCallbackId); |
| get_exception_list_args.Append(kNotifications); |
| handler()->HandleGetExceptionList(get_exception_list_args); |
| |
| const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); |
| |
| ASSERT_TRUE(data.arg3()->is_list()); |
| const base::Value::List& exceptions = data.arg3()->GetList(); |
| ASSERT_EQ(2U, exceptions.size()); |
| |
| validate_exception(exceptions[0]); |
| validate_exception(exceptions[1]); |
| } |
| |
| DestroyIncognitoProfile(); |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, ResetCategoryPermissionForEmbargoedOrigins) { |
| constexpr char kOriginToBlock[] = "https://www.blocked.com:443"; |
| constexpr char kOriginToEmbargo[] = "https://embargoed.co.uk"; |
| |
| // Add and test 1 blocked origin |
| { |
| base::Value::List set_args; |
| set_args.Append(kOriginToBlock); // Primary pattern. |
| set_args.Append(std::string()); // Secondary pattern. |
| set_args.Append(kNotifications); |
| set_args.Append( |
| content_settings::ContentSettingToString(CONTENT_SETTING_BLOCK)); |
| set_args.Append(false); // Incognito. |
| |
| handler()->HandleSetCategoryPermissionForPattern(set_args); |
| ASSERT_EQ(2U, web_ui()->call_data().size()); |
| // When HandleSetCategoryPermissionForPattern is called for a notification |
| // permission, there are two callbacks that make call_data size increase |
| // by 2 instead of 1. |
| ValidateCallbacksForNotificationPermission(0); |
| } |
| |
| // Add and test 1 embargoed origin. |
| { |
| auto* auto_blocker = |
| PermissionDecisionAutoBlockerFactory::GetForProfile(profile()); |
| for (size_t i = 0; i < 3; ++i) { |
| auto_blocker->RecordDismissAndEmbargo(GURL(kOriginToEmbargo), |
| kPermissionNotifications, false); |
| } |
| // Check that origin is under embargo. |
| EXPECT_EQ( |
| CONTENT_SETTING_BLOCK, |
| auto_blocker |
| ->GetEmbargoResult(GURL(kOriginToEmbargo), kPermissionNotifications) |
| ->content_setting); |
| } |
| |
| // Check there are 2 blocked origins. |
| { |
| base::Value::List exceptions; |
| site_settings::GetExceptionsForContentType( |
| kPermissionNotifications, profile(), web_ui(), |
| /*incognito=*/false, &exceptions); |
| |
| // The size should be 2, 1st is blocked origin, 2nd is embargoed origin. |
| ASSERT_EQ(2U, exceptions.size()); |
| } |
| |
| { |
| // Reset blocked origin. |
| base::Value::List reset_args; |
| reset_args.Append(kOriginToBlock); |
| reset_args.Append(std::string()); |
| reset_args.Append(kNotifications); |
| reset_args.Append(false); // Incognito. |
| handler()->HandleResetCategoryPermissionForPattern(reset_args); |
| |
| // Check there is 1 blocked origin. |
| base::Value::List exceptions; |
| site_settings::GetExceptionsForContentType( |
| kPermissionNotifications, profile(), web_ui(), |
| /*incognito=*/false, &exceptions); |
| ASSERT_EQ(1U, exceptions.size()); |
| } |
| |
| { |
| // Reset embargoed origin. |
| base::Value::List reset_args; |
| reset_args.Append(kOriginToEmbargo); |
| reset_args.Append(std::string()); |
| reset_args.Append(kNotifications); |
| reset_args.Append(false); // Incognito. |
| handler()->HandleResetCategoryPermissionForPattern(reset_args); |
| |
| // Check that there are no blocked or embargoed origins. |
| base::Value::List exceptions; |
| site_settings::GetExceptionsForContentType( |
| kPermissionNotifications, profile(), web_ui(), |
| /*incognito=*/false, &exceptions); |
| ASSERT_TRUE(exceptions.empty()); |
| } |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, ResetCategoryPermissionForInvalidOrigins) { |
| constexpr char kInvalidOrigin[] = "example.com"; |
| auto url = GURL(kInvalidOrigin); |
| EXPECT_FALSE(url.is_valid()); |
| EXPECT_TRUE(url.is_empty()); |
| |
| base::Value::List set_args; |
| set_args.Append(kInvalidOrigin); // Primary pattern. |
| set_args.Append(std::string()); // Secondary pattern. |
| set_args.Append(kNotifications); |
| set_args.Append( |
| content_settings::ContentSettingToString(CONTENT_SETTING_BLOCK)); |
| set_args.Append(false); // Incognito. |
| |
| handler()->HandleSetCategoryPermissionForPattern(set_args); |
| ASSERT_EQ(2U, web_ui()->call_data().size()); |
| // When HandleSetCategoryPermissionForPattern is called for a notification |
| // permission, there are two callbacks that make call_data size increase |
| // by 2 instead of 1. |
| ValidateCallbacksForNotificationPermission(0); |
| |
| // Reset blocked origin. |
| base::Value::List reset_args; |
| reset_args.Append(kInvalidOrigin); |
| reset_args.Append(std::string()); |
| reset_args.Append(kNotifications); |
| reset_args.Append(false); // Incognito. |
| // Check that this method is not crashing for an invalid origin. |
| handler()->HandleResetCategoryPermissionForPattern(reset_args); |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, Origins) { |
| const std::string google("https://www.google.com:443"); |
| { |
| // Test the JS -> C++ -> JS callback path for configuring origins, by |
| // setting Google.com to blocked. |
| base::Value::List set_args; |
| set_args.Append(google); // Primary pattern. |
| set_args.Append(std::string()); // Secondary pattern. |
| set_args.Append(kNotifications); |
| set_args.Append( |
| content_settings::ContentSettingToString(CONTENT_SETTING_BLOCK)); |
| set_args.Append(false); // Incognito. |
| handler()->HandleSetCategoryPermissionForPattern(set_args); |
| EXPECT_EQ(2U, web_ui()->call_data().size()); |
| // When HandleSetCategoryPermissionForPattern is called for a notification |
| // permission, there are two callbacks that make call_data size increase |
| // by 2 instead of 1. |
| ValidateCallbacksForNotificationPermission(0); |
| } |
| |
| base::Value::List get_exception_list_args; |
| get_exception_list_args.Append(kCallbackId); |
| get_exception_list_args.Append(kNotifications); |
| handler()->HandleGetExceptionList(get_exception_list_args); |
| ValidateOrigin(google, "", google, CONTENT_SETTING_BLOCK, |
| site_settings::SiteSettingSource::kPreference, 3U); |
| |
| { |
| // Reset things back to how they were. |
| base::Value::List reset_args; |
| reset_args.Append(google); |
| reset_args.Append(std::string()); |
| reset_args.Append(kNotifications); |
| reset_args.Append(false); // Incognito. |
| handler()->HandleResetCategoryPermissionForPattern(reset_args); |
| EXPECT_EQ(5U, web_ui()->call_data().size()); |
| // When HandleResetCategoryPermissionForPattern is called for a notification |
| // permission, there are two callbacks that make call_data size increase |
| // by 2 instead of 1. |
| ValidateCallbacksForNotificationPermission(3); |
| } |
| |
| // Verify the reset was successful. |
| handler()->HandleGetExceptionList(get_exception_list_args); |
| ValidateNoOrigin(6U); |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, NotificationPermissionRevokeUkm) { |
| const std::string google("https://www.google.com"); |
| ukm::TestAutoSetUkmRecorder ukm_recorder; |
| auto* history_service = HistoryServiceFactory::GetForProfile( |
| profile(), ServiceAccessType::EXPLICIT_ACCESS); |
| history_service->AddPage(GURL(google), base::Time::Now(), |
| history::SOURCE_BROWSED); |
| base::RunLoop origin_queried_waiter; |
| history_service->set_origin_queried_closure_for_testing( |
| origin_queried_waiter.QuitClosure()); |
| |
| { |
| base::Value::List set_notification_origin_args; |
| set_notification_origin_args.Append(google); |
| set_notification_origin_args.Append(""); |
| set_notification_origin_args.Append(kNotifications); |
| set_notification_origin_args.Append( |
| content_settings::ContentSettingToString(CONTENT_SETTING_ALLOW)); |
| set_notification_origin_args.Append(false /* incognito */); |
| handler()->HandleSetCategoryPermissionForPattern( |
| set_notification_origin_args); |
| } |
| |
| { |
| base::Value::List set_notification_origin_args; |
| set_notification_origin_args.Append(google); |
| set_notification_origin_args.Append(""); |
| set_notification_origin_args.Append(kNotifications); |
| set_notification_origin_args.Append( |
| content_settings::ContentSettingToString(CONTENT_SETTING_BLOCK)); |
| set_notification_origin_args.Append(false /* incognito */); |
| handler()->HandleSetCategoryPermissionForPattern( |
| set_notification_origin_args); |
| } |
| |
| origin_queried_waiter.Run(); |
| |
| auto entries = ukm_recorder.GetEntriesByName("Permission"); |
| EXPECT_EQ(1u, entries.size()); |
| auto* entry = entries.front(); |
| |
| ukm_recorder.ExpectEntrySourceHasUrl(entry, GURL(google)); |
| EXPECT_EQ( |
| *ukm_recorder.GetEntryMetric(entry, "Source"), |
| static_cast<int64_t>(permissions::PermissionSourceUI::SITE_SETTINGS)); |
| size_t num_values = 0; |
| EXPECT_EQ(*ukm_recorder.GetEntryMetric(entry, "PermissionType"), |
| ContentSettingTypeToHistogramValue( |
| ContentSettingsType::NOTIFICATIONS, &num_values)); |
| EXPECT_EQ(*ukm_recorder.GetEntryMetric(entry, "Action"), |
| static_cast<int64_t>(permissions::PermissionAction::REVOKED)); |
| } |
| |
| // TODO(crbug.com/1076294): Test flakes on TSAN and ASAN. |
| #if defined(THREAD_SANITIZER) || defined(ADDRESS_SANITIZER) |
| #define MAYBE_DefaultSettingSource DISABLED_DefaultSettingSource |
| #else |
| #define MAYBE_DefaultSettingSource DefaultSettingSource |
| #endif |
| TEST_F(SiteSettingsHandlerTest, MAYBE_DefaultSettingSource) { |
| // Use a non-default port to verify the display name does not strip this |
| // off. |
| const std::string google("https://www.google.com:183"); |
| const std::string expected_display_name("www.google.com:183"); |
| |
| ContentSettingSourceSetter source_setter(profile(), |
| ContentSettingsType::NOTIFICATIONS); |
| |
| base::Value::List get_origin_permissions_args; |
| get_origin_permissions_args.Append(kCallbackId); |
| get_origin_permissions_args.Append(google); |
| base::Value::List category_list; |
| category_list.Append(kNotifications); |
| get_origin_permissions_args.Append(std::move(category_list)); |
| |
| // Test Chrome built-in defaults are marked as default. |
| handler()->HandleGetOriginPermissions(get_origin_permissions_args); |
| ValidateOrigin(google, google, expected_display_name, CONTENT_SETTING_ASK, |
| site_settings::SiteSettingSource::kDefault, 1U); |
| |
| base::Value::List default_value_args; |
| default_value_args.Append(kNotifications); |
| default_value_args.Append( |
| content_settings::ContentSettingToString(CONTENT_SETTING_BLOCK)); |
| handler()->HandleSetDefaultValueForContentType(default_value_args); |
| // A user-set global default should also show up as default. |
| handler()->HandleGetOriginPermissions(get_origin_permissions_args); |
| ValidateOrigin(google, google, expected_display_name, CONTENT_SETTING_BLOCK, |
| site_settings::SiteSettingSource::kDefault, 3U); |
| |
| base::Value::List set_notification_pattern_args; |
| set_notification_pattern_args.Append("[*.]google.com"); |
| set_notification_pattern_args.Append(""); |
| set_notification_pattern_args.Append(kNotifications); |
| set_notification_pattern_args.Append( |
| content_settings::ContentSettingToString(CONTENT_SETTING_ALLOW)); |
| set_notification_pattern_args.Append(false); |
| handler()->HandleSetCategoryPermissionForPattern( |
| set_notification_pattern_args); |
| ASSERT_EQ(5U, web_ui()->call_data().size()); |
| // When HandleSetCategoryPermissionForPattern is called for a notification |
| // permission, there are two callbacks that make call_data size increase |
| // by 2 instead of 1. |
| ValidateCallbacksForNotificationPermission(3); |
| // A user-set pattern should not show up as default. |
| handler()->HandleGetOriginPermissions(get_origin_permissions_args); |
| ValidateOrigin(google, google, expected_display_name, CONTENT_SETTING_ALLOW, |
| site_settings::SiteSettingSource::kPreference, 6U); |
| |
| base::Value::List set_notification_origin_args; |
| set_notification_origin_args.Append(google); |
| set_notification_origin_args.Append(""); |
| set_notification_origin_args.Append(kNotifications); |
| set_notification_origin_args.Append( |
| content_settings::ContentSettingToString(CONTENT_SETTING_BLOCK)); |
| set_notification_origin_args.Append(false); |
| handler()->HandleSetCategoryPermissionForPattern( |
| set_notification_origin_args); |
| ASSERT_EQ(8U, web_ui()->call_data().size()); |
| // When HandleSetCategoryPermissionForPattern is called for a notification |
| // permission, there are two callbacks that make call_data size increase |
| // by 2 instead of 1. |
| ValidateCallbacksForNotificationPermission(6); |
| // A user-set per-origin permission should not show up as default. |
| handler()->HandleGetOriginPermissions(get_origin_permissions_args); |
| ValidateOrigin(google, google, expected_display_name, CONTENT_SETTING_BLOCK, |
| site_settings::SiteSettingSource::kPreference, 9U); |
| |
| // Enterprise-policy set defaults should not show up as default. |
| source_setter.SetPolicyDefault(CONTENT_SETTING_ALLOW); |
| handler()->HandleGetOriginPermissions(get_origin_permissions_args); |
| ValidateOrigin(google, google, expected_display_name, CONTENT_SETTING_ALLOW, |
| site_settings::SiteSettingSource::kPolicy, 10U); |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, GetAndSetOriginPermissions) { |
| const std::string origin_with_port("https://www.example.com:443"); |
| // The display name won't show the port if it's default for that scheme. |
| const std::string origin("www.example.com"); |
| base::Value::List get_args; |
| get_args.Append(kCallbackId); |
| get_args.Append(origin_with_port); |
| { |
| base::Value::List category_list; |
| category_list.Append(kNotifications); |
| get_args.Append(std::move(category_list)); |
| } |
| handler()->HandleGetOriginPermissions(get_args); |
| ValidateOrigin(origin_with_port, origin_with_port, origin, |
| CONTENT_SETTING_ASK, |
| site_settings::SiteSettingSource::kDefault, 1U); |
| |
| // Block notifications. |
| base::Value::List set_args; |
| set_args.Append(origin_with_port); |
| set_args.Append(kNotifications); |
| set_args.Append( |
| content_settings::ContentSettingToString(CONTENT_SETTING_BLOCK)); |
| handler()->HandleSetOriginPermissions(set_args); |
| EXPECT_EQ(2U, web_ui()->call_data().size()); |
| |
| // Reset things back to how they were. |
| base::Value::List reset_args; |
| reset_args.Append(origin_with_port); |
| reset_args.Append(std::move(kNotifications)); |
| reset_args.Append( |
| content_settings::ContentSettingToString(CONTENT_SETTING_DEFAULT)); |
| |
| handler()->HandleSetOriginPermissions(reset_args); |
| EXPECT_EQ(3U, web_ui()->call_data().size()); |
| |
| // Verify the reset was successful. |
| handler()->HandleGetOriginPermissions(get_args); |
| ValidateOrigin(origin_with_port, origin_with_port, origin, |
| CONTENT_SETTING_ASK, |
| site_settings::SiteSettingSource::kDefault, 4U); |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, GetAndSetForInvalidURLs) { |
| const std::string origin("arbitrary string"); |
| EXPECT_FALSE(GURL(origin).is_valid()); |
| base::Value::List get_args; |
| get_args.Append(kCallbackId); |
| get_args.Append(origin); |
| { |
| base::Value::List category_list; |
| category_list.Append(kNotifications); |
| get_args.Append(std::move(category_list)); |
| } |
| handler()->HandleGetOriginPermissions(get_args); |
| // Verify that it'll return CONTENT_SETTING_BLOCK as |origin| is not a secure |
| // context, a requirement for notifications. Note that the display string |
| // will be blank since it's an invalid URL. |
| ValidateOrigin(origin, origin, "", CONTENT_SETTING_BLOCK, |
| site_settings::SiteSettingSource::kInsecureOrigin, 1U); |
| |
| // Make sure setting a permission on an invalid origin doesn't crash. |
| base::Value::List set_args; |
| set_args.Append(origin); |
| { |
| base::Value::List category_list; |
| category_list.Append(kNotifications); |
| set_args.Append(std::move(category_list)); |
| } |
| set_args.Append( |
| content_settings::ContentSettingToString(CONTENT_SETTING_ALLOW)); |
| handler()->HandleSetOriginPermissions(set_args); |
| |
| // Also make sure the content setting for |origin| wasn't actually changed. |
| handler()->HandleGetOriginPermissions(get_args); |
| ValidateOrigin(origin, origin, "", CONTENT_SETTING_BLOCK, |
| site_settings::SiteSettingSource::kInsecureOrigin, 2U); |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, ExceptionHelpers) { |
| ContentSettingsPattern pattern = |
| ContentSettingsPattern::FromString("[*.]google.com"); |
| base::Value::Dict exception = site_settings::GetExceptionForPage( |
| ContentSettingsType::NOTIFICATIONS, /*profile=*/nullptr, pattern, |
| ContentSettingsPattern::Wildcard(), pattern.ToString(), |
| CONTENT_SETTING_BLOCK, |
| site_settings::SiteSettingSourceToString( |
| site_settings::SiteSettingSource::kPreference), |
| false); |
| |
| CHECK(exception.FindString(site_settings::kOrigin)); |
| CHECK(exception.FindString(site_settings::kDisplayName)); |
| CHECK(exception.FindString(site_settings::kEmbeddingOrigin)); |
| CHECK(exception.FindString(site_settings::kSetting)); |
| CHECK(exception.FindBool(site_settings::kIncognito).has_value()); |
| |
| base::Value::List args; |
| args.Append(*exception.FindString(site_settings::kOrigin)); |
| args.Append(*exception.FindString(site_settings::kEmbeddingOrigin)); |
| args.Append(kNotifications); // Chosen arbitrarily. |
| args.Append(*exception.FindString(site_settings::kSetting)); |
| args.Append(*exception.FindBool(site_settings::kIncognito)); |
| |
| // We don't need to check the results. This is just to make sure it doesn't |
| // crash on the input. |
| handler()->HandleSetCategoryPermissionForPattern(args); |
| |
| scoped_refptr<const extensions::Extension> extension; |
| extension = extensions::ExtensionBuilder() |
| .SetManifest(base::Value::Dict() |
| .Set("name", kExtensionName) |
| .Set("version", "1.0.0") |
| .Set("manifest_version", 2)) |
| .SetID("ahfgeienlihckogmohjhadlkjgocpleb") |
| .Build(); |
| |
| base::Value::List exceptions; |
| site_settings::AddExceptionForHostedApp("[*.]google.com", *extension.get(), |
| &exceptions); |
| |
| const base::Value& dictionary_value = exceptions[0]; |
| CHECK(dictionary_value.is_dict()); |
| const base::Value::Dict& dictionary = dictionary_value.GetDict(); |
| CHECK(dictionary.FindString(site_settings::kOrigin)); |
| CHECK(dictionary.FindString(site_settings::kDisplayName)); |
| CHECK(dictionary.FindString(site_settings::kEmbeddingOrigin)); |
| CHECK(dictionary.FindString(site_settings::kSetting)); |
| CHECK(dictionary.FindBool(site_settings::kIncognito).has_value()); |
| |
| // Again, don't need to check the results. |
| handler()->HandleSetCategoryPermissionForPattern(args); |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, ExtensionDisplayName) { |
| // When the extension is loaded, displayName is the extension's name and id. |
| auto extension = LoadExtension(kExtensionName); |
| auto extension_url = extension->url().spec(); |
| { |
| base::Value::List get_origin_permissions_args; |
| get_origin_permissions_args.Append(kCallbackId); |
| get_origin_permissions_args.Append(extension_url); |
| { |
| base::Value::List category_list; |
| category_list.Append(kNotifications); |
| get_origin_permissions_args.Append(std::move(category_list)); |
| } |
| handler()->HandleGetOriginPermissions(get_origin_permissions_args); |
| std::string expected_display_name = |
| base::StringPrintf("Test Extension (ID: %s)", extension->id().c_str()); |
| ValidateOrigin(extension_url, extension_url, expected_display_name, |
| CONTENT_SETTING_ASK, |
| site_settings::SiteSettingSource::kDefault, 1U); |
| } |
| |
| // When the extension is unloaded, the displayName is the extension's origin. |
| UnloadExtension(extension->id()); |
| { |
| base::Value::List get_origin_permissions_args; |
| get_origin_permissions_args.Append(kCallbackId); |
| get_origin_permissions_args.Append(extension_url); |
| { |
| base::Value::List category_list; |
| category_list.Append(kNotifications); |
| get_origin_permissions_args.Append(std::move(category_list)); |
| } |
| handler()->HandleGetOriginPermissions(get_origin_permissions_args); |
| ValidateOrigin( |
| extension_url, extension_url, |
| base::StringPrintf("chrome-extension://%s", extension->id().c_str()), |
| CONTENT_SETTING_ASK, site_settings::SiteSettingSource::kDefault, 2U); |
| } |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, PatternsAndContentType) { |
| unsigned counter = 1; |
| for (const auto& test_case : kPatternsAndContentTypeTestCases) { |
| base::Value::List args; |
| args.Append(kCallbackId); |
| args.Append(test_case.arguments.pattern); |
| args.Append(test_case.arguments.content_type); |
| handler()->HandleIsPatternValidForType(args); |
| ValidatePattern(test_case.expected.validity, counter, |
| test_case.expected.reason); |
| ++counter; |
| } |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, Incognito) { |
| base::Value::List args; |
| handler()->HandleUpdateIncognitoStatus(args); |
| ValidateIncognitoExists(false, 1U); |
| |
| CreateIncognitoProfile(); |
| ValidateIncognitoExists(true, 2U); |
| |
| DestroyIncognitoProfile(); |
| ValidateIncognitoExists(false, 3U); |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, ZoomLevels) { |
| std::string host("http://www.google.com"); |
| double zoom_level = 1.1; |
| |
| content::HostZoomMap* host_zoom_map = |
| content::HostZoomMap::GetDefaultForBrowserContext(profile()); |
| host_zoom_map->SetZoomLevelForHost(host, zoom_level); |
| ValidateZoom(host, "122%", 1U); |
| |
| base::Value::List args; |
| handler()->HandleFetchZoomLevels(args); |
| ValidateZoom(host, "122%", 2U); |
| |
| args.Append("http://www.google.com"); |
| handler()->HandleRemoveZoomLevel(args); |
| ValidateZoom("", "", 3U); |
| |
| double default_level = host_zoom_map->GetDefaultZoomLevel(); |
| double level = host_zoom_map->GetZoomLevelForHostAndScheme("http", host); |
| EXPECT_EQ(default_level, level); |
| } |
| |
| class SiteSettingsHandlerInfobarTest : public BrowserWithTestWindowTest { |
| public: |
| SiteSettingsHandlerInfobarTest() |
| : kNotifications(site_settings::ContentSettingsTypeToGroupName( |
| ContentSettingsType::NOTIFICATIONS)) {} |
| SiteSettingsHandlerInfobarTest(const SiteSettingsHandlerInfobarTest&) = |
| delete; |
| SiteSettingsHandlerInfobarTest& operator=( |
| const SiteSettingsHandlerInfobarTest&) = delete; |
| void SetUp() override { |
| BrowserWithTestWindowTest::SetUp(); |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| SetUpUserManager(profile()); |
| #endif |
| |
| handler_ = std::make_unique<SiteSettingsHandler>(profile()); |
| handler()->set_web_ui(web_ui()); |
| handler()->AllowJavascript(); |
| web_ui()->ClearTrackedCalls(); |
| |
| window2_ = CreateBrowserWindow(); |
| browser2_ = |
| CreateBrowser(profile(), browser()->type(), false, window2_.get()); |
| window3_ = CreateBrowserWindow(); |
| |
| TestingProfile* profile2_ = CreateProfile2(); |
| browser3_ = |
| CreateBrowser(profile2_, browser()->type(), false, window3_.get()); |
| |
| extensions::TestExtensionSystem* extension_system = |
| static_cast<extensions::TestExtensionSystem*>( |
| extensions::ExtensionSystem::Get(profile())); |
| extension_system->CreateExtensionService( |
| base::CommandLine::ForCurrentProcess(), base::FilePath(), false); |
| } |
| |
| void TearDown() override { |
| // SiteSettingsHandler maintains a HostZoomMap::Subscription internally, so |
| // make sure that's cleared before BrowserContext / profile destruction. |
| handler()->DisallowJavascript(); |
| |
| // Also destroy `browser2_` before the profile. |
| browser2()->tab_strip_model()->CloseAllTabs(); |
| browser2_.reset(); |
| |
| // Destroy `browser3_`. |
| browser3()->tab_strip_model()->CloseAllTabs(); |
| browser3_.reset(); |
| |
| // Browser()'s destruction is handled in |
| // BrowserWithTestWindowTest::TearDown() |
| BrowserWithTestWindowTest::TearDown(); |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| void SetUpUserManager(TestingProfile* profile) { |
| // On ChromeOS a user account is needed in order to check whether the user |
| // account is affiliated with the device owner for the purposes of applying |
| // enterprise policy. |
| constexpr char kTestUserGaiaId[] = "1111111111"; |
| auto fake_user_manager = std::make_unique<ash::FakeChromeUserManager>(); |
| auto* fake_user_manager_ptr = fake_user_manager.get(); |
| scoped_user_manager_ = std::make_unique<user_manager::ScopedUserManager>( |
| std::move(fake_user_manager)); |
| |
| auto account_id = |
| AccountId::FromUserEmailGaiaId(kTestUserEmail, kTestUserGaiaId); |
| fake_user_manager_ptr->AddUserWithAffiliation(account_id, |
| /*is_affiliated=*/true); |
| fake_user_manager_ptr->LoginUser(account_id); |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| infobars::ContentInfoBarManager* GetInfoBarManagerForTab(Browser* browser, |
| int tab_index, |
| GURL* tab_url) { |
| content::WebContents* web_contents = |
| browser->tab_strip_model()->GetWebContentsAt(tab_index); |
| if (tab_url) |
| *tab_url = web_contents->GetLastCommittedURL(); |
| return infobars::ContentInfoBarManager::FromWebContents(web_contents); |
| } |
| |
| content::TestWebUI* web_ui() { return &web_ui_; } |
| |
| SiteSettingsHandler* handler() { return handler_.get(); } |
| |
| Browser* browser2() { return browser2_.get(); } |
| |
| // browser3 is from a different profile `profile2_` than |
| // browser2 and browser() which are from profile() |
| Browser* browser3() { return browser3_.get(); } |
| |
| // Creates the second profile used by this test. The caller doesn't own the |
| // return value. |
| TestingProfile* CreateProfile2() { |
| return profile_manager()->CreateTestingProfile("testing_profile2@test", |
| nullptr, std::u16string(), 0, |
| GetTestingFactories()); |
| } |
| |
| const std::string kNotifications; |
| |
| private: |
| content::TestWebUI web_ui_; |
| std::unique_ptr<SiteSettingsHandler> handler_; |
| std::unique_ptr<BrowserWindow> window2_; |
| std::unique_ptr<Browser> browser2_; |
| std::unique_ptr<BrowserWindow> window3_; |
| std::unique_ptr<Browser> browser3_; |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_; |
| #endif |
| }; |
| |
| TEST_F(SiteSettingsHandlerInfobarTest, SettingPermissionsTriggersInfobar) { |
| // Note all GURLs starting with 'origin' below belong to the same origin. |
| // _____ _______________ ________ ________ ___________ |
| // Window 1: / foo \' origin_anchor \' chrome \' origin \' extension \ |
| // ------------- ----------------------------------------------------- |
| std::string origin_anchor_string = |
| "https://www.example.com/with/path/blah#heading"; |
| const GURL foo("http://foo"); |
| const GURL origin_anchor(origin_anchor_string); |
| const GURL chrome("chrome://about"); |
| const GURL origin("https://www.example.com/"); |
| const GURL extension( |
| "chrome-extension://fooooooooooooooooooooooooooooooo/bar.html"); |
| |
| // Make sure |extension|'s extension ID exists before navigating to it. This |
| // fixes a test timeout that occurs with --enable-browser-side-navigation on. |
| scoped_refptr<const extensions::Extension> test_extension = |
| extensions::ExtensionBuilder("Test") |
| .SetID("fooooooooooooooooooooooooooooooo") |
| .Build(); |
| extensions::ExtensionSystem::Get(profile()) |
| ->extension_service() |
| ->AddExtension(test_extension.get()); |
| |
| // __________ ______________ ___________________ _______ |
| // Window 2: / insecure '/ origin_query \' example_subdomain \' about \ |
| // ------------------------- -------------------------------- |
| const GURL insecure("http://www.example.com/"); |
| const GURL origin_query("https://www.example.com/?param=value"); |
| const GURL example_subdomain("https://subdomain.example.com/"); |
| const GURL about(url::kAboutBlankURL); |
| |
| // Set up. Note AddTab() adds tab at index 0, so add them in reverse order. |
| AddTab(browser(), extension); |
| AddTab(browser(), origin); |
| AddTab(browser(), chrome); |
| AddTab(browser(), origin_anchor); |
| AddTab(browser(), foo); |
| for (int i = 0; i < browser()->tab_strip_model()->count(); ++i) { |
| EXPECT_EQ(0u, |
| GetInfoBarManagerForTab(browser(), i, nullptr)->infobar_count()); |
| } |
| |
| AddTab(browser2(), about); |
| AddTab(browser2(), example_subdomain); |
| AddTab(browser2(), origin_query); |
| AddTab(browser2(), insecure); |
| for (int i = 0; i < browser2()->tab_strip_model()->count(); ++i) { |
| EXPECT_EQ(0u, |
| GetInfoBarManagerForTab(browser2(), i, nullptr)->infobar_count()); |
| } |
| |
| // Block notifications. |
| base::Value::List set_args; |
| set_args.Append(origin_anchor_string); |
| set_args.Append(kNotifications); |
| set_args.Append( |
| content_settings::ContentSettingToString(CONTENT_SETTING_BLOCK)); |
| handler()->HandleSetOriginPermissions(set_args); |
| |
| // Make sure all tabs belonging to the same origin as |origin_anchor| have an |
| // infobar shown. |
| GURL tab_url; |
| for (int i = 0; i < browser()->tab_strip_model()->count(); ++i) { |
| if (i == /*origin_anchor=*/1 || i == /*origin=*/3) { |
| EXPECT_EQ( |
| 1u, GetInfoBarManagerForTab(browser(), i, &tab_url)->infobar_count()); |
| EXPECT_TRUE(url::IsSameOriginWith(origin, tab_url)); |
| } else { |
| EXPECT_EQ( |
| 0u, GetInfoBarManagerForTab(browser(), i, &tab_url)->infobar_count()); |
| EXPECT_FALSE(url::IsSameOriginWith(origin, tab_url)); |
| } |
| } |
| for (int i = 0; i < browser2()->tab_strip_model()->count(); ++i) { |
| if (i == /*origin_query=*/1) { |
| EXPECT_EQ( |
| 1u, |
| GetInfoBarManagerForTab(browser2(), i, &tab_url)->infobar_count()); |
| EXPECT_TRUE(url::IsSameOriginWith(origin, tab_url)); |
| } else { |
| EXPECT_EQ( |
| 0u, |
| GetInfoBarManagerForTab(browser2(), i, &tab_url)->infobar_count()); |
| EXPECT_FALSE(url::IsSameOriginWith(origin, tab_url)); |
| } |
| } |
| |
| // Navigate the |foo| tab to the same origin as |origin_anchor|, and the |
| // |origin_query| tab to a different origin. |
| const GURL origin_path("https://www.example.com/path/to/page.html"); |
| content::WebContents* foo_contents = |
| browser()->tab_strip_model()->GetWebContentsAt(/*index=*/0); |
| NavigateAndCommit(foo_contents, origin_path); |
| |
| const GURL example_without_www("https://example.com/"); |
| content::WebContents* origin_query_contents = |
| browser2()->tab_strip_model()->GetWebContentsAt(/*index=*/1); |
| NavigateAndCommit(origin_query_contents, example_without_www); |
| |
| // Reset all permissions. |
| base::Value::List reset_args; |
| reset_args.Append(origin_anchor_string); |
| base::Value::List category_list; |
| category_list.Append(kNotifications); |
| reset_args.Append(std::move(category_list)); |
| reset_args.Append( |
| content_settings::ContentSettingToString(CONTENT_SETTING_DEFAULT)); |
| handler()->HandleSetOriginPermissions(reset_args); |
| |
| // Check the same tabs (plus the tab navigated to |origin_path|) still have |
| // infobars showing. |
| for (int i = 0; i < browser()->tab_strip_model()->count(); ++i) { |
| if (i == /*origin_path=*/0 || i == /*origin_anchor=*/1 || |
| i == /*origin=*/3) { |
| EXPECT_EQ( |
| 1u, GetInfoBarManagerForTab(browser(), i, &tab_url)->infobar_count()); |
| EXPECT_TRUE(url::IsSameOriginWith(origin, tab_url)); |
| } else { |
| EXPECT_EQ( |
| 0u, GetInfoBarManagerForTab(browser(), i, &tab_url)->infobar_count()); |
| EXPECT_FALSE(url::IsSameOriginWith(origin, tab_url)); |
| } |
| } |
| // The infobar on the original |origin_query| tab (which has now been |
| // navigated to |example_without_www|) should disappear. |
| for (int i = 0; i < browser2()->tab_strip_model()->count(); ++i) { |
| EXPECT_EQ( |
| 0u, GetInfoBarManagerForTab(browser2(), i, &tab_url)->infobar_count()); |
| EXPECT_FALSE(url::IsSameOriginWith(origin, tab_url)); |
| } |
| |
| // Make sure it's the correct infobar that's being shown. |
| EXPECT_EQ(infobars::InfoBarDelegate::PAGE_INFO_INFOBAR_DELEGATE, |
| GetInfoBarManagerForTab(browser(), /*tab_index=*/0, &tab_url) |
| ->infobar_at(0) |
| ->delegate() |
| ->GetIdentifier()); |
| EXPECT_TRUE(url::IsSameOriginWith(origin, tab_url)); |
| } |
| |
| TEST_F(SiteSettingsHandlerInfobarTest, |
| SettingPermissionsDoesNotTriggerInfobarOnDifferentProfile) { |
| // Note all GURLs starting with 'origin' below belong to the same origin. |
| // _______________ |
| // Window 1: / origin_anchor \ |
| // ------------- ----------------------------------------------------- |
| const GURL origin("https://www.example.com/"); |
| std::string origin_anchor_string = |
| "https://www.example.com/with/path/blah#heading"; |
| const GURL origin_anchor(origin_anchor_string); |
| |
| // Different |
| // Profile (2) ______________ |
| // Window 3: / origin_query \ |
| // ------------------------------------------------------------------------- |
| const GURL origin_query("https://www.example.com/?param=value"); |
| |
| // Set up. No info bars. |
| AddTab(browser(), origin_anchor); |
| EXPECT_EQ(0u, |
| GetInfoBarManagerForTab(browser(), 0, nullptr)->infobar_count()); |
| |
| AddTab(browser3(), origin_query); |
| EXPECT_EQ(0u, |
| GetInfoBarManagerForTab(browser3(), 0, nullptr)->infobar_count()); |
| |
| // Block notifications. |
| base::Value::List set_args; |
| set_args.Append(origin_anchor_string); |
| set_args.Append(kNotifications); |
| set_args.Append( |
| content_settings::ContentSettingToString(CONTENT_SETTING_BLOCK)); |
| handler()->HandleSetOriginPermissions(set_args); |
| |
| // Make sure all tabs within the same profile belonging to the same origin |
| // as `origin_anchor` have an infobar shown. |
| GURL tab_url; |
| EXPECT_EQ(1u, |
| GetInfoBarManagerForTab(browser(), 0, &tab_url)->infobar_count()); |
| EXPECT_TRUE(url::IsSameOriginWith(origin, tab_url)); |
| |
| // Make sure all tabs with the same origin as `origin_anchor` that don't |
| // belong to the same profile don't have an infobar shown |
| EXPECT_EQ(0u, |
| GetInfoBarManagerForTab(browser3(), 0, &tab_url)->infobar_count()); |
| EXPECT_TRUE(url::IsSameOriginWith(origin, tab_url)); |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, SessionOnlyException) { |
| const std::string google_with_port("https://www.google.com:443"); |
| base::Value::List set_args; |
| set_args.Append(google_with_port); // Primary pattern. |
| set_args.Append(std::string()); // Secondary pattern. |
| set_args.Append(kCookies); |
| set_args.Append( |
| content_settings::ContentSettingToString(CONTENT_SETTING_SESSION_ONLY)); |
| set_args.Append(false); // Incognito. |
| handler()->HandleSetCategoryPermissionForPattern(set_args); |
| |
| EXPECT_EQ(kNumberContentSettingListeners, web_ui()->call_data().size()); |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, BlockAutoplay_SendOnRequest) { |
| base::Value::List args; |
| handler()->HandleFetchBlockAutoplayStatus(args); |
| |
| // Check that we are checked and enabled. |
| ValidateBlockAutoplay(true, true); |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, BlockAutoplay_SoundSettingUpdate) { |
| SetSoundContentSettingDefault(CONTENT_SETTING_BLOCK); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Check that we are not checked or enabled. |
| ValidateBlockAutoplay(false, false); |
| |
| SetSoundContentSettingDefault(CONTENT_SETTING_ALLOW); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Check that we are checked and enabled. |
| ValidateBlockAutoplay(true, true); |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, BlockAutoplay_PrefUpdate) { |
| profile()->GetPrefs()->SetBoolean(prefs::kBlockAutoplayEnabled, false); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Check that we are not checked but are enabled. |
| ValidateBlockAutoplay(false, true); |
| |
| profile()->GetPrefs()->SetBoolean(prefs::kBlockAutoplayEnabled, true); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Check that we are checked and enabled. |
| ValidateBlockAutoplay(true, true); |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, BlockAutoplay_Update) { |
| EXPECT_TRUE(profile()->GetPrefs()->GetBoolean(prefs::kBlockAutoplayEnabled)); |
| |
| base::Value::List data; |
| data.Append(false); |
| |
| handler()->HandleSetBlockAutoplayEnabled(data); |
| EXPECT_FALSE(profile()->GetPrefs()->GetBoolean(prefs::kBlockAutoplayEnabled)); |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, ExcludeWebUISchemesInLists) { |
| const ContentSettingsType content_settings_type = |
| ContentSettingsType::NOTIFICATIONS; |
| // Register WebUIAllowlist auto-granted permissions. |
| const url::Origin kWebUIOrigins[] = { |
| url::Origin::Create(GURL("chrome://test")), |
| url::Origin::Create(GURL("chrome-untrusted://test")), |
| url::Origin::Create(GURL("devtools://devtools")), |
| }; |
| |
| WebUIAllowlist* allowlist = WebUIAllowlist::GetOrCreate(profile()); |
| for (const url::Origin& origin : kWebUIOrigins) |
| allowlist->RegisterAutoGrantedPermission(origin, content_settings_type); |
| |
| // Verify the auto-granted permissions are registered, and they are indeed |
| // provided by WebUIAllowlist. |
| HostContentSettingsMap* map = |
| HostContentSettingsMapFactory::GetForProfile(profile()); |
| content_settings::SettingInfo info; |
| base::Value value = map->GetWebsiteSetting(kWebUIOrigins[0].GetURL(), |
| kWebUIOrigins[0].GetURL(), |
| content_settings_type, &info); |
| EXPECT_EQ(CONTENT_SETTING_ALLOW, value.GetInt()); |
| EXPECT_EQ(content_settings::SETTING_SOURCE_ALLOWLIST, info.source); |
| |
| // Register an ordinary website permission. |
| const GURL kWebUrl = GURL("https://example.com"); |
| map->SetContentSettingDefaultScope(kWebUrl, kWebUrl, content_settings_type, |
| CONTENT_SETTING_ALLOW); |
| EXPECT_EQ(CONTENT_SETTING_ALLOW, |
| map->GetContentSetting(kWebUrl, kWebUrl, content_settings_type)); |
| |
| // GetAllSites() only returns website exceptions. |
| { |
| base::Value::List get_all_sites_args; |
| get_all_sites_args.Append(kCallbackId); |
| |
| handler()->HandleGetAllSites(get_all_sites_args); |
| |
| const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); |
| const base::Value::List& site_groups = data.arg3()->GetList(); |
| EXPECT_EQ(1UL, site_groups.size()); |
| const base::Value::Dict& first_site_group = site_groups[0].GetDict(); |
| |
| const std::string etld_plus1_string = |
| CHECK_DEREF(first_site_group.FindString("etldPlus1")); |
| EXPECT_EQ("example.com", etld_plus1_string); |
| const base::Value::List& origin_list = |
| CHECK_DEREF(first_site_group.FindList("origins")); |
| EXPECT_EQ(1UL, origin_list.size()); |
| EXPECT_EQ(kWebUrl.spec(), |
| CHECK_DEREF(origin_list[0].GetDict().FindString("origin"))); |
| } |
| |
| // GetExceptionList() only returns website exceptions. |
| { |
| base::Value::List get_exception_list_args; |
| get_exception_list_args.Append(kCallbackId); |
| get_exception_list_args.Append(kNotifications); |
| |
| handler()->HandleGetExceptionList(get_exception_list_args); |
| |
| const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); |
| const base::Value::List& exception_list = data.arg3()->GetList(); |
| EXPECT_EQ(1UL, exception_list.size()); |
| EXPECT_EQ("https://example.com:443", |
| CHECK_DEREF(exception_list[0].GetDict().FindString("origin"))); |
| } |
| |
| // GetRecentSitePermissions() only returns website exceptions. |
| { |
| base::Value::List get_recent_permissions_args; |
| get_recent_permissions_args.Append(kCallbackId); |
| get_recent_permissions_args.Append(3); |
| |
| handler()->HandleGetRecentSitePermissions(get_recent_permissions_args); |
| |
| const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); |
| const base::Value::List& recent_permission_list = data.arg3()->GetList(); |
| EXPECT_EQ(1UL, recent_permission_list.size()); |
| EXPECT_EQ( |
| kWebUrl.spec(), |
| CHECK_DEREF(recent_permission_list[0].GetDict().FindString("origin"))); |
| } |
| } |
| |
| // GetOriginPermissions() returns the allowlisted exception. We explicitly |
| // return this, so developers can easily test things (e.g. by navigating to |
| // chrome://settings/content/siteDetails?site=chrome://example). |
| TEST_F(SiteSettingsHandlerTest, IncludeWebUISchemesInGetOriginPermissions) { |
| const ContentSettingsType content_settings_type = |
| ContentSettingsType::NOTIFICATIONS; |
| |
| // Register WebUIAllowlist auto-granted permissions. |
| const url::Origin kWebUIOrigins[] = { |
| url::Origin::Create(GURL("chrome://test")), |
| url::Origin::Create(GURL("chrome-untrusted://test")), |
| url::Origin::Create(GURL("devtools://devtools")), |
| }; |
| |
| WebUIAllowlist* allowlist = WebUIAllowlist::GetOrCreate(profile()); |
| for (const url::Origin& origin : kWebUIOrigins) |
| allowlist->RegisterAutoGrantedPermission(origin, content_settings_type); |
| |
| for (const url::Origin& origin : kWebUIOrigins) { |
| base::Value::List get_origin_permissions_args; |
| get_origin_permissions_args.Append(kCallbackId); |
| get_origin_permissions_args.Append(origin.GetURL().spec()); |
| base::Value::List category_list; |
| category_list.Append(kNotifications); |
| get_origin_permissions_args.Append(std::move(category_list)); |
| |
| handler()->HandleGetOriginPermissions(get_origin_permissions_args); |
| const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); |
| const base::Value::List& exception_list = data.arg3()->GetList(); |
| EXPECT_EQ(1UL, exception_list.size()); |
| const base::Value::Dict& first_exception = exception_list[0].GetDict(); |
| |
| EXPECT_EQ(origin.GetURL().spec(), |
| CHECK_DEREF(first_exception.FindString("origin"))); |
| EXPECT_EQ("allowlist", CHECK_DEREF(first_exception.FindString("source"))); |
| } |
| } |
| |
| class PersistentPermissionsSiteSettingsHandlerTest |
| : public SiteSettingsHandlerTest { |
| void SetUp() override { |
| SiteSettingsHandlerTest::SetUp(); |
| handler_ = std::make_unique<SiteSettingsHandler>(&profile_); |
| handler_->set_web_ui(web_ui()); |
| handler_->AllowJavascript(); |
| web_ui()->ClearTrackedCalls(); |
| } |
| |
| void TearDown() override { handler_->DisallowJavascript(); } |
| |
| public: |
| PersistentPermissionsSiteSettingsHandlerTest() { |
| // TODO(crbug.com/1373962): Remove this feature list enabler |
| // when Persistent Permissions is launched. |
| |
| // Enable Persisted Permissions. |
| feature_list_.InitAndEnableFeature( |
| features::kFileSystemAccessPersistentPermissions); |
| } |
| |
| protected: |
| std::unique_ptr<SiteSettingsHandler> handler_; |
| TestingProfile profile_; |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| // GetFileSystemGrants() returns the allowed grants for a given origin |
| // based on the File System Access persistent permissions policy. |
| TEST_F(PersistentPermissionsSiteSettingsHandlerTest, |
| HandleGetFileSystemGrants) { |
| ChromeFileSystemAccessPermissionContext* context = |
| FileSystemAccessPermissionContextFactory::GetForProfile(&profile_); |
| |
| auto kTestOrigin1 = url::Origin::Create(GURL("https://www.a.com")); |
| auto kTestOrigin2 = url::Origin::Create(GURL("https://www.b.com")); |
| |
| const base::FilePath kTestPath = base::FilePath(FILE_PATH_LITERAL("/a/b")); |
| const base::FilePath kTestPath2 = base::FilePath(FILE_PATH_LITERAL("/c/d")); |
| const base::FilePath kTestPath3 = base::FilePath(FILE_PATH_LITERAL("/e/")); |
| const base::FilePath kTestPath4 = |
| base::FilePath(FILE_PATH_LITERAL("/f/g/h/")); |
| |
| // Populate the `grants` object with permissions. |
| auto file_read_grant = context->GetPersistedReadPermissionGrantForTesting( |
| kTestOrigin1, kTestPath, |
| ChromeFileSystemAccessPermissionContext::HandleType::kFile); |
| auto file_write_grant = context->GetPersistedWritePermissionGrantForTesting( |
| kTestOrigin2, kTestPath2, |
| ChromeFileSystemAccessPermissionContext::HandleType::kFile); |
| auto directory_read_grant = |
| context->GetPersistedReadPermissionGrantForTesting( |
| kTestOrigin1, kTestPath3, |
| ChromeFileSystemAccessPermissionContext::HandleType::kDirectory); |
| auto directory_write_grant = |
| context->GetPersistedWritePermissionGrantForTesting( |
| kTestOrigin2, kTestPath4, |
| ChromeFileSystemAccessPermissionContext::HandleType::kDirectory); |
| |
| EXPECT_EQ(context->GetPermissionGrants(kTestOrigin1).file_read_grants.size(), |
| 1UL); |
| EXPECT_EQ(context->GetPermissionGrants(kTestOrigin2).file_read_grants.size(), |
| 0UL); |
| EXPECT_EQ(context->GetPermissionGrants(kTestOrigin1).file_write_grants.size(), |
| 0UL); |
| EXPECT_EQ(context->GetPermissionGrants(kTestOrigin2).file_write_grants.size(), |
| 1UL); |
| EXPECT_EQ( |
| context->GetPermissionGrants(kTestOrigin1).directory_read_grants.size(), |
| 1UL); |
| EXPECT_EQ( |
| context->GetPermissionGrants(kTestOrigin2).directory_read_grants.size(), |
| 0UL); |
| EXPECT_EQ( |
| context->GetPermissionGrants(kTestOrigin1).directory_write_grants.size(), |
| 0UL); |
| EXPECT_EQ( |
| context->GetPermissionGrants(kTestOrigin2).directory_write_grants.size(), |
| 1UL); |
| |
| base::Value::List get_file_system_permissions_args; |
| get_file_system_permissions_args.Append(kCallbackId); |
| |
| handler_->HandleGetFileSystemGrants(get_file_system_permissions_args); |
| const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); |
| const base::Value::List& grants = data.arg3()->GetList(); |
| |
| EXPECT_EQ(grants.size(), 2UL); |
| const base::Value::Dict& first_grant = grants[0].GetDict(); |
| const base::Value::Dict& second_grant = grants[1].GetDict(); |
| EXPECT_EQ(CHECK_DEREF(first_grant.FindString(site_settings::kOrigin)), |
| "https://www.a.com/"); |
| EXPECT_EQ(CHECK_DEREF(second_grant.FindString(site_settings::kOrigin)), |
| "https://www.b.com/"); |
| |
| const base::Value::List* kTestOrigin1FileReadGrants = |
| first_grant.FindList(site_settings::kFileReadGrants); |
| const base::Value::List* kTestOrigin1FileWriteGrants = |
| first_grant.FindList(site_settings::kFileWriteGrants); |
| const base::Value::List* kTestOrigin1DirectoryReadGrants = |
| first_grant.FindList(site_settings::kDirectoryReadGrants); |
| const base::Value::List* kTestOrigin1DirectoryWriteGrants = |
| first_grant.FindList(site_settings::kDirectoryWriteGrants); |
| |
| const base::Value::List* kTestOrigin2FileReadGrants = |
| second_grant.FindList(site_settings::kFileReadGrants); |
| const base::Value::List* kTestOrigin2FileWriteGrants = |
| second_grant.FindList(site_settings::kFileWriteGrants); |
| const base::Value::List* kTestOrigin2DirectoryReadGrants = |
| second_grant.FindList(site_settings::kDirectoryReadGrants); |
| const base::Value::List* kTestOrigin2DirectoryWriteGrants = |
| second_grant.FindList(site_settings::kDirectoryWriteGrants); |
| |
| // Checks that the grants for test origins are populated as expected. |
| EXPECT_FALSE(CHECK_DEREF(kTestOrigin1FileReadGrants)[0] |
| .GetDict() |
| .FindBool(site_settings::kIsDirectory) |
| .value_or(true)); |
| ASSERT_TRUE(kTestOrigin1FileWriteGrants != nullptr); |
| EXPECT_TRUE(kTestOrigin1FileWriteGrants->empty()); |
| EXPECT_EQ( |
| CHECK_DEREF( |
| CHECK_DEREF(kTestOrigin1DirectoryReadGrants)[0].GetDict().FindString( |
| site_settings::kFilePath)), |
| "/e/"); |
| ASSERT_TRUE(kTestOrigin1DirectoryWriteGrants != nullptr); |
| EXPECT_TRUE(kTestOrigin1DirectoryWriteGrants->empty()); |
| |
| // In the case of kTestOrigin2, check that when an origin has an |
| // associated 'write' grant, that the grant is only recorded in the |
| // respective write grants list, and is not recorded in the origin's |
| // read grants list. |
| ASSERT_TRUE(kTestOrigin2FileReadGrants != nullptr); |
| EXPECT_TRUE(kTestOrigin2FileReadGrants->empty()); |
| EXPECT_TRUE(CHECK_DEREF(kTestOrigin2FileWriteGrants)[0] |
| .GetDict() |
| .FindBool(site_settings::kIsWritable) |
| .value_or(false)); |
| ASSERT_TRUE(kTestOrigin2DirectoryReadGrants != nullptr); |
| EXPECT_TRUE(kTestOrigin2DirectoryReadGrants->empty()); |
| EXPECT_TRUE(CHECK_DEREF(kTestOrigin2DirectoryWriteGrants)[0] |
| .GetDict() |
| .FindBool(site_settings::kIsDirectory) |
| .value_or(false)); |
| } |
| |
| // RevokeGrant() revokes a single File System Access permission grant, |
| // for a given origin and file path. |
| TEST_F(PersistentPermissionsSiteSettingsHandlerTest, |
| HandleRevokeFileSystemGrant) { |
| ChromeFileSystemAccessPermissionContext* context = |
| FileSystemAccessPermissionContextFactory::GetForProfile(&profile_); |
| |
| auto kTestOrigin1 = url::Origin::Create(GURL("https://www.a.com")); |
| auto kTestOrigin2 = url::Origin::Create(GURL("https://www.b.com")); |
| |
| const base::FilePath kTestPath = base::FilePath(FILE_PATH_LITERAL("/a/b")); |
| const base::FilePath kTestPath2 = base::FilePath(FILE_PATH_LITERAL("/c/d/")); |
| const base::FilePath kTestPath3 = base::FilePath(FILE_PATH_LITERAL("/e/")); |
| const base::FilePath kTestPath4 = |
| base::FilePath(FILE_PATH_LITERAL("/f/g/h/")); |
| |
| // Populate the `grants` object with permissions. |
| auto file_read_grant = context->GetPersistedReadPermissionGrantForTesting( |
| kTestOrigin1, kTestPath, |
| ChromeFileSystemAccessPermissionContext::HandleType::kFile); |
| auto directory_read_grant = |
| context->GetPersistedReadPermissionGrantForTesting( |
| kTestOrigin1, kTestPath2, |
| ChromeFileSystemAccessPermissionContext::HandleType::kDirectory); |
| auto directory_write_grant = |
| context->GetPersistedWritePermissionGrantForTesting( |
| kTestOrigin2, kTestPath3, |
| ChromeFileSystemAccessPermissionContext::HandleType::kDirectory); |
| auto second_directory_write_grant = |
| context->GetPersistedWritePermissionGrantForTesting( |
| kTestOrigin2, kTestPath4, |
| ChromeFileSystemAccessPermissionContext::HandleType::kDirectory); |
| |
| base::Value::List revoke_origin1_grant_permissions_args; |
| revoke_origin1_grant_permissions_args.Append("https://www.a.com"); |
| revoke_origin1_grant_permissions_args.Append("/a/b"); |
| |
| base::Value::List get_file_system_grants_permissions_args; |
| get_file_system_grants_permissions_args.Append(kCallbackId); |
| |
| handler_->HandleRevokeFileSystemGrant(revoke_origin1_grant_permissions_args); |
| handler_->HandleGetFileSystemGrants(get_file_system_grants_permissions_args); |
| const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); |
| const base::Value::List& grants = data.arg3()->GetList(); |
| |
| // After revoking the `file_read_grant` for kTestOrigin1, only one |
| // `directory_read_grant` should remain when retrieving the file system grants |
| // for kTestOrigin1. |
| EXPECT_TRUE( |
| grants[0].GetDict().FindList(site_settings::kFileReadGrants)->empty()); |
| EXPECT_EQ( |
| grants[0].GetDict().FindList(site_settings::kDirectoryReadGrants)->size(), |
| 1UL); |
| |
| // Revoking a single grant from an origin with multiple grants in a given |
| // grants list only revokes the grant with the given file path. |
| // In this case, for kTestOrigin2, only the directory write grant for |
| // kTestPath2 is revoked, and the directory write grant with kTestPath4 |
| // remains in the `directory_write_grants` list. |
| base::Value::List revoke_origin2_grant_permissions_args; |
| revoke_origin2_grant_permissions_args.Append("https://www.b.com"); |
| revoke_origin2_grant_permissions_args.Append("/e/"); |
| |
| handler_->HandleRevokeFileSystemGrant(revoke_origin2_grant_permissions_args); |
| handler_->HandleGetFileSystemGrants(get_file_system_grants_permissions_args); |
| const content::TestWebUI::CallData& updated_data = |
| *web_ui()->call_data().back(); |
| const base::Value::List& updated_grants = updated_data.arg3()->GetList(); |
| |
| EXPECT_EQ(updated_grants[1] |
| .GetDict() |
| .FindList(site_settings::kDirectoryWriteGrants) |
| ->size(), |
| 1UL); |
| EXPECT_EQ(CHECK_DEREF(CHECK_DEREF(updated_grants[1].GetDict().FindList( |
| site_settings::kDirectoryWriteGrants))[0] |
| .GetDict() |
| .FindString(site_settings::kFilePath)), |
| "/f/g/h/"); |
| } |
| |
| // RevokeGrants() revokes all File System Access permission grants, |
| // for a given origin. |
| TEST_F(PersistentPermissionsSiteSettingsHandlerTest, |
| HandleRevokeFileSystemGrants) { |
| ChromeFileSystemAccessPermissionContext* context = |
| FileSystemAccessPermissionContextFactory::GetForProfile(&profile_); |
| |
| auto kTestOrigin1 = url::Origin::Create(GURL("https://www.a.com")); |
| auto kTestOrigin2 = url::Origin::Create(GURL("https://www.b.com")); |
| |
| const base::FilePath kTestPath = base::FilePath(FILE_PATH_LITERAL("/a/b")); |
| const base::FilePath kTestPath2 = base::FilePath(FILE_PATH_LITERAL("/c/d")); |
| const base::FilePath kTestPath3 = base::FilePath(FILE_PATH_LITERAL("/e/")); |
| const base::FilePath kTestPath4 = |
| base::FilePath(FILE_PATH_LITERAL("/f/g/h/")); |
| |
| // Populate the `grants` object with permissions. |
| auto file_read_grant = context->GetPersistedReadPermissionGrantForTesting( |
| kTestOrigin1, kTestPath, |
| ChromeFileSystemAccessPermissionContext::HandleType::kFile); |
| auto file_write_grant = context->GetPersistedWritePermissionGrantForTesting( |
| kTestOrigin2, kTestPath2, |
| ChromeFileSystemAccessPermissionContext::HandleType::kFile); |
| auto directory_read_grant = |
| context->GetPersistedReadPermissionGrantForTesting( |
| kTestOrigin1, kTestPath3, |
| ChromeFileSystemAccessPermissionContext::HandleType::kDirectory); |
| auto directory_write_grant = |
| context->GetPersistedWritePermissionGrantForTesting( |
| kTestOrigin2, kTestPath4, |
| ChromeFileSystemAccessPermissionContext::HandleType::kDirectory); |
| |
| base::Value::List get_file_system_grants_permissions_args; |
| get_file_system_grants_permissions_args.Append(kCallbackId); |
| |
| handler_->HandleGetFileSystemGrants(get_file_system_grants_permissions_args); |
| const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); |
| const base::Value::List& grants = data.arg3()->GetList(); |
| |
| // The number of entries in grants is equal to the number of origins with |
| // permission grants, before revoking grants for a given origin. |
| EXPECT_EQ(grants.size(), 2UL); |
| |
| base::Value::List revoke_origin1_grants_permissions_args; |
| revoke_origin1_grants_permissions_args.Append("https://www.a.com"); |
| |
| handler_->HandleRevokeFileSystemGrants( |
| revoke_origin1_grants_permissions_args); |
| handler_->HandleGetFileSystemGrants(get_file_system_grants_permissions_args); |
| const content::TestWebUI::CallData& updated_data = |
| *web_ui()->call_data().back(); |
| const base::Value::List& updated_grants = updated_data.arg3()->GetList(); |
| |
| // All grants are revoked for kTestOrigin1, and the grants for kTestOrigin2 |
| // are unaffected. |
| EXPECT_EQ(updated_grants.size(), 1UL); |
| EXPECT_EQ(updated_grants[0] |
| .GetDict() |
| .FindList(site_settings::kFileWriteGrants) |
| ->size(), |
| 1UL); |
| EXPECT_EQ(updated_grants[0] |
| .GetDict() |
| .FindList(site_settings::kDirectoryWriteGrants) |
| ->size(), |
| 1UL); |
| } |
| |
| namespace { |
| |
| std::vector<std::string> GetExceptionDisplayNames( |
| const base::Value::List& exceptions) { |
| std::vector<std::string> display_names; |
| for (const base::Value& exception : exceptions) { |
| const std::string* display_name = |
| exception.GetDict().FindString(site_settings::kDisplayName); |
| if (display_name) { |
| display_names.push_back(*display_name); |
| } |
| } |
| return display_names; |
| } |
| |
| } // namespace |
| |
| class SiteSettingsHandlerChooserExceptionTest |
| : public SiteSettingsHandlerBaseTest { |
| protected: |
| const GURL kAndroidUrl{"https://android.com"}; |
| const GURL kChromiumUrl{"https://chromium.org"}; |
| const GURL kGoogleUrl{"https://google.com"}; |
| const GURL kWebUIUrl{"chrome://test"}; |
| |
| void SetUp() override { |
| SiteSettingsHandlerBaseTest::SetUp(); |
| SetUpChooserContext(); |
| SetUpPolicyGrantedPermissions(); |
| |
| // Add the observer for permission changes. |
| GetChooserContext(profile())->AddObserver(&observer_); |
| } |
| |
| void TearDown() override { |
| GetChooserContext(profile())->RemoveObserver(&observer_); |
| SiteSettingsHandlerBaseTest::TearDown(); |
| } |
| |
| void DestroyIncognitoProfile() override { |
| GetChooserContext(incognito_profile())->RemoveObserver(&observer_); |
| SiteSettingsHandlerBaseTest::DestroyIncognitoProfile(); |
| } |
| |
| // Call SiteSettingsHandler::HandleGetChooserExceptionList for |chooser_type| |
| // and return the exception list received by the WebUI. |
| void ValidateChooserExceptionList(const std::string& chooser_type, |
| size_t expected_total_calls) { |
| base::Value::List args; |
| args.Append(kCallbackId); |
| args.Append(chooser_type); |
| |
| handler()->HandleGetChooserExceptionList(args); |
| |
| EXPECT_EQ(web_ui()->call_data().size(), expected_total_calls); |
| |
| const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); |
| EXPECT_EQ("cr.webUIResponse", data.function_name()); |
| |
| ASSERT_TRUE(data.arg1()); |
| ASSERT_TRUE(data.arg1()->is_string()); |
| EXPECT_EQ(data.arg1()->GetString(), kCallbackId); |
| |
| ASSERT_TRUE(data.arg2()); |
| ASSERT_TRUE(data.arg2()->is_bool()); |
| EXPECT_TRUE(data.arg2()->GetBool()); |
| |
| ASSERT_TRUE(data.arg3()); |
| ASSERT_TRUE(data.arg3()->is_list()); |
| } |
| |
| const base::Value::List& GetChooserExceptionListFromWebUiCallData( |
| const std::string& chooser_type, |
| size_t expected_total_calls) { |
| ValidateChooserExceptionList(chooser_type, expected_total_calls); |
| return web_ui()->call_data().back()->arg3()->GetList(); |
| } |
| |
| // Iterate through the exception's sites array and return true if a site |
| // exception matches |requesting_origin| and |embedding_origin|. |
| bool ChooserExceptionContainsSiteException(const base::Value::Dict& exception, |
| base::StringPiece origin) { |
| const base::Value::List* sites = exception.FindList(site_settings::kSites); |
| if (!sites) |
| return false; |
| |
| for (const auto& site : *sites) { |
| const std::string* exception_origin = |
| site.GetDict().FindString(site_settings::kOrigin); |
| if (!exception_origin) |
| continue; |
| if (*exception_origin == origin) |
| return true; |
| } |
| return false; |
| } |
| |
| // Iterate through the |exception_list| array and return true if there is a |
| // chooser exception with |display_name| that contains a site exception for |
| // |origin|. |
| bool ChooserExceptionContainsSiteException( |
| const base::Value::List& exceptions, |
| base::StringPiece display_name, |
| base::StringPiece origin) { |
| for (const auto& exception : exceptions) { |
| const std::string* exception_display_name = |
| exception.GetDict().FindString(site_settings::kDisplayName); |
| if (!exception_display_name) |
| continue; |
| |
| if (*exception_display_name == display_name) { |
| return ChooserExceptionContainsSiteException(exception.GetDict(), |
| origin); |
| } |
| } |
| return false; |
| } |
| |
| void TestHandleGetChooserExceptionList() { |
| AddPersistentDevice(); |
| AddEphemeralDevice(); |
| AddUserGrantedDevice(); |
| base::RunLoop().RunUntilIdle(); |
| |
| SetUpUserGrantedPermissions(); |
| |
| base::RunLoop().RunUntilIdle(); |
| web_ui()->ClearTrackedCalls(); |
| |
| const std::string group_name( |
| site_settings::ContentSettingsTypeToGroupName(content_type())); |
| |
| const base::Value::List& exceptions = |
| GetChooserExceptionListFromWebUiCallData(group_name, |
| /*expected_total_calls=*/1u); |
| |
| // There are 9 granted permissions: |
| // 1. Persistent permission for persistent-device on kChromiumOrigin |
| // 2. Persistent permission for persistent-device on kGoogleOrigin |
| // 3. Persistent permission for persistent-device on kWebUIOrigin |
| // 4. Persistent permission for user-granted-device on kAndroidOrigin |
| // 5. Ephemeral permission for ephemeral-device on kAndroidOrigin |
| // 6. Policy-granted permission for any device on kGoogleOrigin |
| // 7. Policy-granted permission for vendor 18D1 on kAndroidOrigin |
| // 8. Policy-granted permission for vendor 18D2 on kAndroidOrigin |
| // 9. Policy-granted permission for device 18D1:162E on kChromiumOrigin |
| // |
| // Permission 3 is ignored by GetChooserExceptionListFromProfile because its |
| // origin has a WebUI scheme (chrome://). |
| // |
| // Some of the user-granted permissions are redundant due to policy-granted |
| // permissions. Permission 1 is redundant due to permission 9; 2 is |
| // redundant due to 6; 5 is redundant due to 8. UsbChooserContext omits |
| // redundant items but other APIs do not. This causes |
| // GetChooserExceptionListForProfile to return a different number of |
| // exceptions depending on the API. |
| // |
| // UsbChooserContext also detects when a policy-granted permission refers to |
| // the same origin and device IDs as a user-granted permission. When |
| // deduplicating redundant exceptions, the user-granted exception display |
| // name is used because it contains the device name. |
| // |
| // TODO(https://crbug.com/1392442): Update SerialChooserContext and |
| // HidChooserContext to deduplicate redundant exceptions. |
| switch (content_type()) { |
| case ContentSettingsType::BLUETOOTH_CHOOSER_DATA: |
| // BluetoothChooserContext creates a different permission object for |
| // each (device,origin) pair, so persistent-device shows up for each of |
| // kChromiumOrigin and kGoogleOrigin. |
| // |
| // TODO(https://crbug.com/1040174): No policy-granted exceptions are |
| // included because Web Bluetooth does not support granting device |
| // permissions by policy. |
| EXPECT_THAT( |
| GetExceptionDisplayNames(exceptions), |
| UnorderedElementsAre("persistent-device", "persistent-device", |
| "ephemeral-device", "user-granted-device")); |
| break; |
| case ContentSettingsType::HID_CHOOSER_DATA: |
| case ContentSettingsType::SERIAL_CHOOSER_DATA: |
| EXPECT_THAT( |
| GetExceptionDisplayNames(exceptions), |
| UnorderedElementsAre("persistent-device", "user-granted-device", |
| "ephemeral-device", GetAllDevicesDisplayName(), |
| GetDevicesFromGoogleDisplayName(), |
| GetDevicesFromVendor18D2DisplayName(), |
| GetUnknownProductDisplayName())); |
| break; |
| case ContentSettingsType::USB_CHOOSER_DATA: |
| EXPECT_THAT( |
| GetExceptionDisplayNames(exceptions), |
| UnorderedElementsAre("persistent-device", "user-granted-device", |
| GetAllDevicesDisplayName(), |
| GetDevicesFromGoogleDisplayName(), |
| GetDevicesFromVendor18D2DisplayName())); |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| |
| // Don't include WebUI schemes. |
| const std::string kWebUIOriginStr = |
| kWebUIUrl.DeprecatedGetOriginAsURL().spec(); |
| EXPECT_FALSE(ChooserExceptionContainsSiteException( |
| exceptions, "persistent-device", kWebUIOriginStr)); |
| } |
| |
| void TestHandleGetChooserExceptionListForOffTheRecord() { |
| SetUpOffTheRecordChooserContext(); |
| AddPersistentDevice(); |
| AddEphemeralDevice(); |
| AddUserGrantedDevice(); |
| AddOffTheRecordDevice(); |
| base::RunLoop().RunUntilIdle(); |
| |
| SetUpUserGrantedPermissions(); |
| |
| base::RunLoop().RunUntilIdle(); |
| web_ui()->ClearTrackedCalls(); |
| |
| const std::string group_name( |
| site_settings::ContentSettingsTypeToGroupName(content_type())); |
| |
| // The objects returned by GetChooserExceptionListFromProfile should also |
| // include the incognito permissions. |
| { |
| const base::Value::List& exceptions = |
| GetChooserExceptionListFromWebUiCallData(group_name, |
| /*expected_total_calls=*/1u); |
| switch (content_type()) { |
| case ContentSettingsType::BLUETOOTH_CHOOSER_DATA: |
| EXPECT_THAT( |
| GetExceptionDisplayNames(exceptions), |
| UnorderedElementsAre("persistent-device", "persistent-device", |
| "ephemeral-device", "user-granted-device", |
| "off-the-record-device")); |
| break; |
| case ContentSettingsType::HID_CHOOSER_DATA: |
| case ContentSettingsType::SERIAL_CHOOSER_DATA: |
| EXPECT_THAT( |
| GetExceptionDisplayNames(exceptions), |
| UnorderedElementsAre( |
| "persistent-device", "ephemeral-device", |
| "user-granted-device", "off-the-record-device", |
| GetAllDevicesDisplayName(), GetDevicesFromGoogleDisplayName(), |
| GetDevicesFromVendor18D2DisplayName(), |
| GetUnknownProductDisplayName())); |
| break; |
| case ContentSettingsType::USB_CHOOSER_DATA: |
| EXPECT_THAT(GetExceptionDisplayNames(exceptions), |
| UnorderedElementsAre( |
| "persistent-device", "user-granted-device", |
| "off-the-record-device", GetAllDevicesDisplayName(), |
| GetDevicesFromGoogleDisplayName(), |
| GetDevicesFromVendor18D2DisplayName(), |
| GetUnknownProductDisplayName())); |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| // Destroy the off the record profile and check that the objects returned do |
| // not include incognito permissions anymore. The destruction of the profile |
| // causes the "onIncognitoStatusChanged" WebUIListener callback to fire. |
| DestroyIncognitoProfile(); |
| EXPECT_EQ(web_ui()->call_data().size(), 2u); |
| |
| { |
| const base::Value::List& exceptions = |
| GetChooserExceptionListFromWebUiCallData(group_name, |
| /*expected_total_calls=*/3u); |
| switch (content_type()) { |
| case ContentSettingsType::BLUETOOTH_CHOOSER_DATA: |
| EXPECT_THAT( |
| GetExceptionDisplayNames(exceptions), |
| UnorderedElementsAre("persistent-device", "persistent-device", |
| "ephemeral-device", "user-granted-device")); |
| break; |
| case ContentSettingsType::HID_CHOOSER_DATA: |
| case ContentSettingsType::SERIAL_CHOOSER_DATA: |
| EXPECT_THAT(GetExceptionDisplayNames(exceptions), |
| UnorderedElementsAre( |
| "persistent-device", "ephemeral-device", |
| "user-granted-device", GetAllDevicesDisplayName(), |
| GetDevicesFromGoogleDisplayName(), |
| GetDevicesFromVendor18D2DisplayName(), |
| GetUnknownProductDisplayName())); |
| break; |
| case ContentSettingsType::USB_CHOOSER_DATA: |
| EXPECT_THAT( |
| GetExceptionDisplayNames(exceptions), |
| UnorderedElementsAre("persistent-device", "user-granted-device", |
| GetAllDevicesDisplayName(), |
| GetDevicesFromGoogleDisplayName(), |
| GetDevicesFromVendor18D2DisplayName())); |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| } |
| } |
| |
| void TestHandleResetChooserExceptionForSite() { |
| AddPersistentDevice(); |
| AddEphemeralDevice(); |
| AddUserGrantedDevice(); |
| base::RunLoop().RunUntilIdle(); |
| |
| SetUpUserGrantedPermissions(); |
| |
| base::RunLoop().RunUntilIdle(); |
| web_ui()->ClearTrackedCalls(); |
| |
| const std::string group_name( |
| site_settings::ContentSettingsTypeToGroupName(content_type())); |
| const auto kAndroidOrigin = url::Origin::Create(kAndroidUrl); |
| const auto kChromiumOrigin = url::Origin::Create(kChromiumUrl); |
| const auto kGoogleOrigin = url::Origin::Create(kGoogleUrl); |
| const std::string kAndroidOriginStr = |
| kAndroidUrl.DeprecatedGetOriginAsURL().spec(); |
| const std::string kChromiumOriginStr = |
| kChromiumUrl.DeprecatedGetOriginAsURL().spec(); |
| const std::string kGoogleOriginStr = |
| kGoogleUrl.DeprecatedGetOriginAsURL().spec(); |
| |
| { |
| const base::Value::List& exceptions = |
| GetChooserExceptionListFromWebUiCallData(group_name, |
| /*expected_total_calls=*/1u); |
| switch (content_type()) { |
| case ContentSettingsType::BLUETOOTH_CHOOSER_DATA: |
| EXPECT_THAT( |
| GetExceptionDisplayNames(exceptions), |
| UnorderedElementsAre("persistent-device", "persistent-device", |
| "ephemeral-device", "user-granted-device")); |
| break; |
| case ContentSettingsType::HID_CHOOSER_DATA: |
| case ContentSettingsType::SERIAL_CHOOSER_DATA: |
| EXPECT_THAT(GetExceptionDisplayNames(exceptions), |
| UnorderedElementsAre( |
| "persistent-device", "ephemeral-device", |
| "user-granted-device", GetAllDevicesDisplayName(), |
| GetDevicesFromGoogleDisplayName(), |
| GetDevicesFromVendor18D2DisplayName(), |
| GetUnknownProductDisplayName())); |
| break; |
| case ContentSettingsType::USB_CHOOSER_DATA: |
| EXPECT_THAT( |
| GetExceptionDisplayNames(exceptions), |
| UnorderedElementsAre("persistent-device", "user-granted-device", |
| GetAllDevicesDisplayName(), |
| GetDevicesFromGoogleDisplayName(), |
| GetDevicesFromVendor18D2DisplayName())); |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| // User granted USB permissions for devices also containing policy |
| // permissions should be able to be reset without removing the chooser |
| // exception object from the list. |
| base::Value::List args; |
| args.Append(group_name); |
| args.Append(kGoogleOriginStr); |
| args.Append(GetPersistentDeviceValueForOrigin(kGoogleOrigin)); |
| |
| EXPECT_CALL(observer_, |
| OnObjectPermissionChanged({guard_type()}, content_type())); |
| EXPECT_CALL(observer_, OnPermissionRevoked(kGoogleOrigin)); |
| handler()->HandleResetChooserExceptionForSite(args); |
| GetChooserContext(profile())->FlushScheduledSaveSettingsCalls(); |
| |
| // The HandleResetChooserExceptionForSite() method should have also caused |
| // the WebUIListenerCallbacks for contentSettingSitePermissionChanged and |
| // contentSettingChooserPermissionChanged to fire. |
| EXPECT_EQ(web_ui()->call_data().size(), 3u); |
| { |
| // The exception list size should not have been reduced since there is |
| // still a policy granted permission for "persistent-device". |
| const base::Value::List& exceptions = |
| GetChooserExceptionListFromWebUiCallData(group_name, |
| /*expected_total_calls=*/4u); |
| switch (content_type()) { |
| case ContentSettingsType::BLUETOOTH_CHOOSER_DATA: |
| EXPECT_THAT( |
| GetExceptionDisplayNames(exceptions), |
| UnorderedElementsAre("persistent-device", "ephemeral-device", |
| "user-granted-device")); |
| break; |
| case ContentSettingsType::HID_CHOOSER_DATA: |
| case ContentSettingsType::SERIAL_CHOOSER_DATA: |
| EXPECT_THAT(GetExceptionDisplayNames(exceptions), |
| UnorderedElementsAre( |
| "persistent-device", "ephemeral-device", |
| "user-granted-device", GetAllDevicesDisplayName(), |
| GetDevicesFromGoogleDisplayName(), |
| GetDevicesFromVendor18D2DisplayName(), |
| GetUnknownProductDisplayName())); |
| break; |
| case ContentSettingsType::USB_CHOOSER_DATA: |
| EXPECT_THAT( |
| GetExceptionDisplayNames(exceptions), |
| UnorderedElementsAre("persistent-device", "user-granted-device", |
| GetAllDevicesDisplayName(), |
| GetDevicesFromGoogleDisplayName(), |
| GetDevicesFromVendor18D2DisplayName())); |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| |
| // Ensure that the sites list does not contain the URLs of the removed |
| // permission. |
| EXPECT_FALSE(ChooserExceptionContainsSiteException( |
| exceptions, "persistent-device", kGoogleOriginStr)); |
| |
| // User granted exceptions that are also granted by policy are only |
| // displayed through the policy granted site exception, so ensure that the |
| // policy exception is present under "persistent-device". |
| if (content_type() != ContentSettingsType::BLUETOOTH_CHOOSER_DATA) { |
| EXPECT_TRUE(ChooserExceptionContainsSiteException( |
| exceptions, "persistent-device", kChromiumOriginStr)); |
| } |
| } |
| |
| // Try revoking the user-granted permission for persistent-device. There is |
| // also a policy-granted permission for the same device. |
| args.clear(); |
| args.Append(group_name); |
| args.Append(kChromiumOriginStr); |
| args.Append(GetPersistentDeviceValueForOrigin(kChromiumOrigin)); |
| |
| EXPECT_CALL(observer_, |
| OnObjectPermissionChanged({guard_type()}, content_type())); |
| EXPECT_CALL(observer_, OnPermissionRevoked(kChromiumOrigin)); |
| handler()->HandleResetChooserExceptionForSite(args); |
| GetChooserContext(profile())->FlushScheduledSaveSettingsCalls(); |
| |
| // The HandleResetChooserExceptionForSite() method should have also caused |
| // the WebUIListenerCallbacks for contentSettingSitePermissionChanged and |
| // contentSettingChooserPermissionChanged to fire. |
| EXPECT_EQ(web_ui()->call_data().size(), 6u); |
| { |
| const base::Value::List& exceptions = |
| GetChooserExceptionListFromWebUiCallData(group_name, |
| /*expected_total_calls=*/7u); |
| switch (content_type()) { |
| case ContentSettingsType::BLUETOOTH_CHOOSER_DATA: |
| EXPECT_THAT( |
| GetExceptionDisplayNames(exceptions), |
| UnorderedElementsAre("ephemeral-device", "user-granted-device")); |
| break; |
| case ContentSettingsType::HID_CHOOSER_DATA: |
| case ContentSettingsType::SERIAL_CHOOSER_DATA: |
| EXPECT_THAT( |
| GetExceptionDisplayNames(exceptions), |
| UnorderedElementsAre("ephemeral-device", "user-granted-device", |
| GetAllDevicesDisplayName(), |
| GetDevicesFromGoogleDisplayName(), |
| GetDevicesFromVendor18D2DisplayName(), |
| GetUnknownProductDisplayName())); |
| break; |
| case ContentSettingsType::USB_CHOOSER_DATA: |
| EXPECT_THAT(GetExceptionDisplayNames(exceptions), |
| UnorderedElementsAre( |
| "user-granted-device", GetAllDevicesDisplayName(), |
| GetDevicesFromGoogleDisplayName(), |
| GetDevicesFromVendor18D2DisplayName(), |
| GetUnknownProductDisplayName())); |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| |
| // Ensure that the sites list still displays a site exception entry for an |
| // origin of kGoogleOriginStr. Since now the device has had its |
| // permission revoked, the policy-provided object will not be able to |
| // deduce the name "persistent-device" from the connected device. As such |
| // we check that the policy is still active by looking for the genericly |
| // constructed name. |
| if (content_type() != ContentSettingsType::BLUETOOTH_CHOOSER_DATA) { |
| EXPECT_TRUE(ChooserExceptionContainsSiteException( |
| exceptions, GetUnknownProductDisplayName(), kChromiumOriginStr)); |
| EXPECT_FALSE(ChooserExceptionContainsSiteException( |
| exceptions, "persistent-device", kGoogleOriginStr)); |
| } |
| |
| // Ensure the exception for user-granted-device on kAndroidOrigin is |
| // present since we will try to revoke it. |
| EXPECT_TRUE(ChooserExceptionContainsSiteException( |
| exceptions, "user-granted-device", kAndroidOriginStr)); |
| } |
| |
| // User granted USB permissions that are not covered by policy should be |
| // able to be reset and the chooser exception entry should be removed from |
| // the list when the exception only has one site exception granted to it. |
| args.clear(); |
| args.Append(group_name); |
| args.Append(kAndroidOriginStr); |
| args.Append(GetUserGrantedDeviceValueForOrigin(kAndroidOrigin)); |
| |
| EXPECT_CALL(observer_, |
| OnObjectPermissionChanged({guard_type()}, content_type())); |
| EXPECT_CALL(observer_, OnPermissionRevoked(kAndroidOrigin)); |
| handler()->HandleResetChooserExceptionForSite(args); |
| GetChooserContext(profile())->FlushScheduledSaveSettingsCalls(); |
| |
| // The HandleResetChooserExceptionForSite() method should have also caused |
| // the WebUIListenerCallbacks for contentSettingSitePermissionChanged and |
| // contentSettingChooserPermissionChanged to fire. |
| EXPECT_EQ(web_ui()->call_data().size(), 9u); |
| { |
| const base::Value::List& exceptions = |
| GetChooserExceptionListFromWebUiCallData( |
| group_name, /*expected_total_calls=*/10u); |
| switch (content_type()) { |
| case ContentSettingsType::BLUETOOTH_CHOOSER_DATA: |
| EXPECT_THAT(GetExceptionDisplayNames(exceptions), |
| UnorderedElementsAre("ephemeral-device")); |
| break; |
| case ContentSettingsType::HID_CHOOSER_DATA: |
| case ContentSettingsType::SERIAL_CHOOSER_DATA: |
| EXPECT_THAT(GetExceptionDisplayNames(exceptions), |
| UnorderedElementsAre( |
| "ephemeral-device", GetAllDevicesDisplayName(), |
| GetDevicesFromGoogleDisplayName(), |
| GetDevicesFromVendor18D2DisplayName(), |
| GetUnknownProductDisplayName())); |
| break; |
| case ContentSettingsType::USB_CHOOSER_DATA: |
| EXPECT_THAT( |
| GetExceptionDisplayNames(exceptions), |
| UnorderedElementsAre(GetAllDevicesDisplayName(), |
| GetDevicesFromGoogleDisplayName(), |
| GetDevicesFromVendor18D2DisplayName(), |
| GetUnknownProductDisplayName())); |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| EXPECT_FALSE(ChooserExceptionContainsSiteException( |
| exceptions, "user-granted-device", kAndroidOriginStr)); |
| } |
| } |
| |
| void TestHandleSetOriginPermissions() { |
| constexpr base::StringPiece kYoutubeOriginStr = "https://youtube.com/"; |
| const GURL kYoutubeUrl{kYoutubeOriginStr}; |
| const auto kYoutubeOrigin = url::Origin::Create(kYoutubeUrl); |
| |
| // Grant permissions for user-granted-device on `kYoutubeOrigin`. |
| AddUserGrantedDevice(); |
| SetUpUserGrantedPermissionForOrigin(kYoutubeOrigin); |
| |
| base::RunLoop().RunUntilIdle(); |
| web_ui()->ClearTrackedCalls(); |
| |
| const std::string group_name( |
| site_settings::ContentSettingsTypeToGroupName(content_type())); |
| |
| { |
| const base::Value::List& exceptions = |
| GetChooserExceptionListFromWebUiCallData(group_name, |
| /*expected_total_calls=*/1u); |
| switch (content_type()) { |
| case ContentSettingsType::BLUETOOTH_CHOOSER_DATA: |
| EXPECT_THAT(GetExceptionDisplayNames(exceptions), |
| UnorderedElementsAre("user-granted-device")); |
| break; |
| case ContentSettingsType::HID_CHOOSER_DATA: |
| case ContentSettingsType::SERIAL_CHOOSER_DATA: |
| case ContentSettingsType::USB_CHOOSER_DATA: |
| EXPECT_THAT(GetExceptionDisplayNames(exceptions), |
| UnorderedElementsAre( |
| "user-granted-device", GetAllDevicesDisplayName(), |
| GetDevicesFromGoogleDisplayName(), |
| GetDevicesFromVendor18D2DisplayName(), |
| GetUnknownProductDisplayName())); |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| EXPECT_TRUE(ChooserExceptionContainsSiteException( |
| exceptions, "user-granted-device", kYoutubeOriginStr)); |
| } |
| |
| // Clear data for kYoutubeOrigin. The permission should be revoked. |
| base::Value::List args; |
| args.Append(kYoutubeOriginStr); |
| args.Append(base::Value()); |
| args.Append( |
| content_settings::ContentSettingToString(CONTENT_SETTING_DEFAULT)); |
| |
| EXPECT_CALL(observer_, |
| OnObjectPermissionChanged({guard_type()}, content_type())); |
| EXPECT_CALL(observer_, OnPermissionRevoked(kYoutubeOrigin)); |
| handler()->HandleSetOriginPermissions(args); |
| GetChooserContext(profile())->FlushScheduledSaveSettingsCalls(); |
| |
| // HandleSetOriginPermissions caused WebUIListenerCallbacks: |
| // * contentSettingsChooserPermissionChanged once |
| // * contentSettingsSitePermissionChanged for each visible content type |
| // * contentSettingsSitePermissionChanged again for `content_type()` |
| const size_t kContentSettingsTypeCount = |
| site_settings::GetVisiblePermissionCategories().size(); |
| EXPECT_EQ(kContentSettingsTypeCount + 3, web_ui()->call_data().size()); |
| { |
| const base::Value::List& exceptions = |
| GetChooserExceptionListFromWebUiCallData( |
| group_name, |
| /*expected_total_calls=*/kContentSettingsTypeCount + 4); |
| switch (content_type()) { |
| case ContentSettingsType::BLUETOOTH_CHOOSER_DATA: |
| EXPECT_TRUE(exceptions.empty()); |
| break; |
| case ContentSettingsType::HID_CHOOSER_DATA: |
| case ContentSettingsType::SERIAL_CHOOSER_DATA: |
| case ContentSettingsType::USB_CHOOSER_DATA: |
| EXPECT_THAT( |
| GetExceptionDisplayNames(exceptions), |
| UnorderedElementsAre(GetAllDevicesDisplayName(), |
| GetDevicesFromGoogleDisplayName(), |
| GetDevicesFromVendor18D2DisplayName(), |
| GetUnknownProductDisplayName())); |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| EXPECT_FALSE(ChooserExceptionContainsSiteException( |
| exceptions, "user-granted-device", kYoutubeOriginStr)); |
| } |
| } |
| |
| void TestHandleSetOriginPermissionsPolicyOnly() { |
| const auto kGoogleOrigin = url::Origin::Create(kGoogleUrl); |
| const std::string kGoogleOriginStr = |
| kGoogleUrl.DeprecatedGetOriginAsURL().spec(); |
| |
| base::RunLoop().RunUntilIdle(); |
| web_ui()->ClearTrackedCalls(); |
| |
| const std::string group_name( |
| site_settings::ContentSettingsTypeToGroupName(content_type())); |
| |
| { |
| const base::Value::List& exceptions = |
| GetChooserExceptionListFromWebUiCallData(group_name, |
| /*expected_total_calls=*/1u); |
| switch (content_type()) { |
| case ContentSettingsType::BLUETOOTH_CHOOSER_DATA: |
| EXPECT_TRUE(exceptions.empty()); |
| break; |
| case ContentSettingsType::HID_CHOOSER_DATA: |
| case ContentSettingsType::SERIAL_CHOOSER_DATA: |
| case ContentSettingsType::USB_CHOOSER_DATA: |
| EXPECT_THAT( |
| GetExceptionDisplayNames(exceptions), |
| UnorderedElementsAre(GetAllDevicesDisplayName(), |
| GetDevicesFromGoogleDisplayName(), |
| GetDevicesFromVendor18D2DisplayName(), |
| GetUnknownProductDisplayName())); |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| // Clear data for kGoogleOrigin. |
| base::Value::List args; |
| args.Append(kGoogleOriginStr); |
| args.Append(base::Value()); |
| args.Append( |
| content_settings::ContentSettingToString(CONTENT_SETTING_DEFAULT)); |
| |
| EXPECT_CALL(observer_, OnObjectPermissionChanged).Times(0); |
| EXPECT_CALL(observer_, OnPermissionRevoked).Times(0); |
| handler()->HandleSetOriginPermissions(args); |
| GetChooserContext(profile())->FlushScheduledSaveSettingsCalls(); |
| |
| // HandleSetOriginPermissions caused WebUIListenerCallbacks: |
| // * contentSettingsSitePermissionChanged for each visible content type |
| const size_t kContentSettingsTypeCount = |
| site_settings::GetVisiblePermissionCategories().size(); |
| EXPECT_EQ(kContentSettingsTypeCount + 1, web_ui()->call_data().size()); |
| { |
| const base::Value::List& exceptions = |
| GetChooserExceptionListFromWebUiCallData( |
| group_name, |
| /*expected_total_calls=*/kContentSettingsTypeCount + 2); |
| switch (content_type()) { |
| case ContentSettingsType::BLUETOOTH_CHOOSER_DATA: |
| EXPECT_TRUE(exceptions.empty()); |
| break; |
| case ContentSettingsType::HID_CHOOSER_DATA: |
| case ContentSettingsType::SERIAL_CHOOSER_DATA: |
| case ContentSettingsType::USB_CHOOSER_DATA: |
| EXPECT_THAT( |
| GetExceptionDisplayNames(exceptions), |
| UnorderedElementsAre(GetAllDevicesDisplayName(), |
| GetDevicesFromGoogleDisplayName(), |
| GetDevicesFromVendor18D2DisplayName(), |
| GetUnknownProductDisplayName())); |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| } |
| } |
| |
| // Returns the ContentSettingsType for the chooser data (device permissions). |
| virtual ContentSettingsType content_type() = 0; |
| |
| // Returns the ContentSettingsType for the guard permission. |
| virtual ContentSettingsType guard_type() = 0; |
| |
| // Returns the chooser context for `profile`. |
| virtual permissions::ObjectPermissionContextBase* GetChooserContext( |
| Profile* profile) = 0; |
| |
| // Sets up the chooser context. |
| virtual void SetUpChooserContext() = 0; |
| |
| // Creates an incognito profile and sets up the chooser context for that |
| // profile. Tests must call DestroyIncognitoProfile before exiting. |
| virtual void SetUpOffTheRecordChooserContext() {} |
| |
| // Grants permissions used by tests. There are four devices that are |
| // granted user permissions. Two (persistent-device and ephemeral-device) are |
| // covered by different policy permissions, while the third |
| // (user-granted-device) is not covered by policy at all. If the |
| // off-the-record-device is present, a user-granted permission is granted for |
| // the incognito profile. |
| virtual void SetUpUserGrantedPermissions() = 0; |
| |
| // Configures policies to automatically grant device permissions. |
| virtual void SetUpPolicyGrantedPermissions() {} |
| |
| // Grants device permissions for user-granted-device on `origin`. |
| virtual void SetUpUserGrantedPermissionForOrigin( |
| const url::Origin& origin) = 0; |
| |
| // Create and add a device eligible for persistent permissions. The device |
| // name is "persistent-device". |
| virtual void AddPersistentDevice() = 0; |
| |
| // Create and add a device ineligible for persistent permissions. The device |
| // name is "ephemeral-device". |
| virtual void AddEphemeralDevice() = 0; |
| |
| // Create and add a device. The permission policies added in |
| // `SetUpPolicyGrantedPermissions` should not grant permissions for this |
| // device except for policies which affect all devices. The device name is |
| // "user-granted-device". |
| virtual void AddUserGrantedDevice() = 0; |
| |
| // Create and add a device. Tests should only grant permissions for this |
| // device using the off the record profile. The device name is |
| // "off-the-record-device". |
| virtual void AddOffTheRecordDevice() = 0; |
| |
| // Returns the permission object representing persistent-device granted to |
| // `origin`. |
| virtual base::Value GetPersistentDeviceValueForOrigin( |
| const url::Origin& origin) = 0; |
| |
| // Returns the permission object representing user-granted-device granted to |
| // `origin`. |
| virtual base::Value GetUserGrantedDeviceValueForOrigin( |
| const url::Origin& origin) = 0; |
| |
| // Returns the display name for a chooser exception that allows an origin to |
| // access any device. |
| virtual std::string GetAllDevicesDisplayName() { return {}; } |
| |
| // Returns the display name for a chooser exception that allows an origin to |
| // access any device with the Google vendor ID. |
| virtual std::string GetDevicesFromGoogleDisplayName() { return {}; } |
| |
| // Returns the display name for a chooser exception that allows an origin to |
| // access any device from vendor 0x18D2. |
| virtual std::string GetDevicesFromVendor18D2DisplayName() { return {}; } |
| |
| // Returns the display name for a chooser exception that allows an origin to |
| // access a specific device by its vendor and product IDs. |
| virtual std::string GetUnknownProductDisplayName() { return {}; } |
| |
| permissions::MockPermissionObserver observer_; |
| }; |
| |
| class SiteSettingsHandlerBluetoothTest |
| : public SiteSettingsHandlerChooserExceptionTest { |
| protected: |
| SiteSettingsHandlerBluetoothTest() |
| : SiteSettingsHandlerChooserExceptionTest() { |
| feature_list_.InitAndEnableFeature( |
| features::kWebBluetoothNewPermissionsBackend); |
| } |
| |
| permissions::ObjectPermissionContextBase* GetChooserContext( |
| Profile* profile) override { |
| return BluetoothChooserContextFactory::GetForProfile(profile); |
| } |
| |
| void SetUpChooserContext() override { |
| adapter_ = base::MakeRefCounted<NiceMock<device::MockBluetoothAdapter>>(); |
| EXPECT_CALL(*adapter_, IsPresent).WillRepeatedly(Return(true)); |
| EXPECT_CALL(*adapter_, IsPowered).WillRepeatedly(Return(true)); |
| device::BluetoothAdapterFactory::SetAdapterForTesting(adapter_); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void SetUpOffTheRecordChooserContext() override { |
| CreateIncognitoProfile(); |
| GetChooserContext(incognito_profile())->AddObserver(&observer_); |
| } |
| |
| void AddPersistentDevice() override { |
| persistent_device_ = |
| std::make_unique<NiceMock<device::MockBluetoothDevice>>( |
| adapter_.get(), /*bluetooth_class=*/0, /*name=*/"persistent-device", |
| /*address=*/"1", /*paired=*/false, /*connected=*/false); |
| } |
| |
| void AddEphemeralDevice() override { |
| ephemeral_device_ = std::make_unique<NiceMock<device::MockBluetoothDevice>>( |
| adapter_.get(), /*bluetooth_class=*/0, /*name=*/"ephemeral-device", |
| /*address=*/"2", /*paired=*/false, /*connected=*/false); |
| } |
| |
| void AddUserGrantedDevice() override { |
| user_granted_device_ = |
| std::make_unique<NiceMock<device::MockBluetoothDevice>>( |
| adapter_.get(), /*bluetooth_class=*/0, |
| /*name=*/"user-granted-device", /*address=*/"3", /*paired=*/false, |
| /*connected=*/false); |
| } |
| |
| void AddOffTheRecordDevice() override { |
| off_the_record_device_ = |
| std::make_unique<NiceMock<device::MockBluetoothDevice>>( |
| adapter_.get(), /*bluetooth_class=*/0, |
| /*name=*/"off-the-record-device", /*address=*/"4", /*paired=*/false, |
| /*connected=*/false); |
| } |
| |
| void SetUpUserGrantedPermissions() override { |
| const auto kAndroidOrigin = url::Origin::Create(kAndroidUrl); |
| const auto kChromiumOrigin = url::Origin::Create(kChromiumUrl); |
| const auto kGoogleOrigin = url::Origin::Create(kGoogleUrl); |
| const auto kWebUIOrigin = url::Origin::Create(kWebUIUrl); |
| |
| auto options = blink::mojom::WebBluetoothRequestDeviceOptions::New(); |
| options->accept_all_devices = true; |
| { |
| base::RunLoop loop; |
| auto barrier_closure = base::BarrierClosure(5, loop.QuitClosure()); |
| auto* bluetooth_chooser_context = |
| BluetoothChooserContextFactory::GetForProfile(profile()); |
| EXPECT_CALL(observer_, OnObjectPermissionChanged( |
| {ContentSettingsType::BLUETOOTH_GUARD}, |
| ContentSettingsType::BLUETOOTH_CHOOSER_DATA)) |
| .Times(5) |
| .WillRepeatedly(RunClosure(barrier_closure)); |
| bluetooth_chooser_context->GrantServiceAccessPermission( |
| kChromiumOrigin, persistent_device_.get(), options.get()); |
| bluetooth_chooser_context->GrantServiceAccessPermission( |
| kGoogleOrigin, persistent_device_.get(), options.get()); |
| bluetooth_chooser_context->GrantServiceAccessPermission( |
| kWebUIOrigin, persistent_device_.get(), options.get()); |
| bluetooth_chooser_context->GrantServiceAccessPermission( |
| kAndroidOrigin, ephemeral_device_.get(), options.get()); |
| bluetooth_chooser_context->GrantServiceAccessPermission( |
| kAndroidOrigin, user_granted_device_.get(), options.get()); |
| loop.Run(); |
| } |
| |
| if (off_the_record_device_) { |
| base::RunLoop loop; |
| EXPECT_CALL(observer_, OnObjectPermissionChanged( |
| {ContentSettingsType::BLUETOOTH_GUARD}, |
| ContentSettingsType::BLUETOOTH_CHOOSER_DATA)) |
| .WillOnce(RunClosure(loop.QuitClosure())); |
| BluetoothChooserContextFactory::GetForProfile(incognito_profile()) |
| ->GrantServiceAccessPermission( |
| kChromiumOrigin, off_the_record_device_.get(), options.get()); |
| loop.Run(); |
| } |
| } |
| |
| void SetUpUserGrantedPermissionForOrigin(const url::Origin& origin) override { |
| auto* bluetooth_chooser_context = |
| BluetoothChooserContextFactory::GetForProfile(profile()); |
| base::RunLoop loop; |
| EXPECT_CALL(observer_, OnObjectPermissionChanged( |
| {ContentSettingsType::BLUETOOTH_GUARD}, |
| ContentSettingsType::BLUETOOTH_CHOOSER_DATA)) |
| .WillOnce(RunClosure(loop.QuitClosure())); |
| auto options = blink::mojom::WebBluetoothRequestDeviceOptions::New(); |
| options->accept_all_devices = true; |
| bluetooth_chooser_context->GrantServiceAccessPermission( |
| origin, user_granted_device_.get(), options.get()); |
| loop.Run(); |
| } |
| |
| base::Value GetPersistentDeviceValueForOrigin( |
| const url::Origin& origin) override { |
| auto options = blink::mojom::WebBluetoothRequestDeviceOptions::New(); |
| options->accept_all_devices = true; |
| auto device_id = |
| BluetoothChooserContextFactory::GetForProfile(profile()) |
| ->GetWebBluetoothDeviceId(origin, persistent_device_->GetAddress()); |
| return base::Value(permissions::BluetoothChooserContext::DeviceInfoToValue( |
| persistent_device_.get(), options.get(), device_id)); |
| } |
| |
| base::Value GetUserGrantedDeviceValueForOrigin( |
| const url::Origin& origin) override { |
| auto options = blink::mojom::WebBluetoothRequestDeviceOptions::New(); |
| options->accept_all_devices = true; |
| auto device_id = BluetoothChooserContextFactory::GetForProfile(profile()) |
| ->GetWebBluetoothDeviceId( |
| origin, user_granted_device_->GetAddress()); |
| return base::Value(permissions::BluetoothChooserContext::DeviceInfoToValue( |
| user_granted_device_.get(), options.get(), device_id)); |
| } |
| |
| ContentSettingsType content_type() override { |
| return ContentSettingsType::BLUETOOTH_CHOOSER_DATA; |
| } |
| |
| ContentSettingsType guard_type() override { |
| return ContentSettingsType::BLUETOOTH_GUARD; |
| } |
| |
| base::test::ScopedFeatureList feature_list_; |
| scoped_refptr<NiceMock<device::MockBluetoothAdapter>> adapter_; |
| std::unique_ptr<NiceMock<device::MockBluetoothDevice>> ephemeral_device_; |
| std::unique_ptr<NiceMock<device::MockBluetoothDevice>> off_the_record_device_; |
| std::unique_ptr<NiceMock<device::MockBluetoothDevice>> persistent_device_; |
| std::unique_ptr<NiceMock<device::MockBluetoothDevice>> user_granted_device_; |
| }; |
| |
| TEST_F(SiteSettingsHandlerBluetoothTest, HandleGetChooserExceptionList) { |
| TestHandleGetChooserExceptionList(); |
| } |
| |
| TEST_F(SiteSettingsHandlerBluetoothTest, |
| HandleGetChooserExceptionListForOffTheRecord) { |
| TestHandleGetChooserExceptionListForOffTheRecord(); |
| } |
| |
| TEST_F(SiteSettingsHandlerBluetoothTest, HandleResetChooserExceptionForSite) { |
| TestHandleResetChooserExceptionForSite(); |
| } |
| |
| TEST_F(SiteSettingsHandlerBluetoothTest, HandleSetOriginPermissions) { |
| TestHandleSetOriginPermissions(); |
| } |
| |
| TEST_F(SiteSettingsHandlerBluetoothTest, HandleSetOriginPermissionsPolicyOnly) { |
| TestHandleSetOriginPermissionsPolicyOnly(); |
| } |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| class SiteSettingsHandlerHidTest |
| : public SiteSettingsHandlerChooserExceptionTest { |
| protected: |
| void SetUpChooserContext() override { |
| mojo::PendingRemote<device::mojom::HidManager> hid_manager; |
| hid_manager_.AddReceiver(hid_manager.InitWithNewPipeAndPassReceiver()); |
| HidChooserContext* hid_chooser_context = |
| HidChooserContextFactory::GetForProfile(profile()); |
| TestFuture<std::vector<device::mojom::HidDeviceInfoPtr>> get_devices_future; |
| hid_chooser_context->SetHidManagerForTesting( |
| std::move(hid_manager), get_devices_future.GetCallback()); |
| EXPECT_TRUE(get_devices_future.Wait()); |
| } |
| |
| void SetUpPolicyGrantedPermissions() override { |
| auto* local_state = TestingBrowserProcess::GetGlobal()->local_state(); |
| ASSERT_TRUE(local_state); |
| local_state->Set(prefs::kManagedWebHidAllowDevicesForUrls, ParseJson(R"( |
| [ |
| { |
| "devices": [{ "vendor_id": 6353, "product_id": 5678 }], |
| "urls": ["https://chromium.org"] |
| }, { |
| "devices": [{ "vendor_id": 6353 }], |
| "urls": ["https://android.com"] |
| }, { |
| "devices": [{ "vendor_id": 6354 }], |
| "urls": ["https://android.com"] |
| } |
| ])")); |
| local_state->Set(prefs::kManagedWebHidAllowAllDevicesForUrls, |
| ParseJson(R"([ "https://google.com" ])")); |
| } |
| |
| void SetUpOffTheRecordChooserContext() override { |
| CreateIncognitoProfile(); |
| mojo::PendingRemote<device::mojom::HidManager> hid_manager; |
| hid_manager_.AddReceiver(hid_manager.InitWithNewPipeAndPassReceiver()); |
| HidChooserContext* hid_chooser_context = |
| HidChooserContextFactory::GetForProfile(incognito_profile()); |
| TestFuture<std::vector<device::mojom::HidDeviceInfoPtr>> get_devices_future; |
| hid_chooser_context->SetHidManagerForTesting( |
| std::move(hid_manager), get_devices_future.GetCallback()); |
| EXPECT_TRUE(get_devices_future.Wait()); |
| GetChooserContext(incognito_profile())->AddObserver(&observer_); |
| } |
| |
| void AddPersistentDevice() override { |
| persistent_device_ = hid_manager_.CreateAndAddDevice( |
| /*physical_device_id=*/"1", /*vendor_id=*/6353, /*product_id=*/5678, |
| /*product_name=*/"persistent-device", /*serial_number=*/"123ABC", |
| device::mojom::HidBusType::kHIDBusTypeUSB); |
| } |
| |
| void AddEphemeralDevice() override { |
| ephemeral_device_ = hid_manager_.CreateAndAddDevice( |
| /*physical_device_id=*/"2", /*vendor_id=*/6354, /*product_id=*/0, |
| /*product_name=*/"ephemeral-device", /*serial_number=*/"", |
| device::mojom::HidBusType::kHIDBusTypeUSB); |
| } |
| |
| void AddUserGrantedDevice() override { |
| user_granted_device_ = hid_manager_.CreateAndAddDevice( |
| /*physical_device_id=*/"3", /*vendor_id=*/6355, /*product_id=*/0, |
| /*product_name=*/"user-granted-device", /*serial_number=*/"789XYZ", |
| device::mojom::HidBusType::kHIDBusTypeUSB); |
| } |
| |
| void AddOffTheRecordDevice() override { |
| off_the_record_device_ = hid_manager_.CreateAndAddDevice( |
| /*physical_device_id=*/"4", /*vendor_id=*/6353, |
| /*product_id=*/8765, /*product_name=*/"off-the-record-device", |
| /*serial_number=*/"A9B8C7", device::mojom::HidBusType::kHIDBusTypeUSB); |
| } |
| |
| void SetUpUserGrantedPermissions() override { |
| const auto kAndroidOrigin = url::Origin::Create(kAndroidUrl); |
| const auto kChromiumOrigin = url::Origin::Create(kChromiumUrl); |
| const auto kGoogleOrigin = url::Origin::Create(kGoogleUrl); |
| const auto kWebUIOrigin = url::Origin::Create(kWebUIUrl); |
| |
| // Add the user granted permissions for testing. |
| // These two persistent device permissions should be lumped together |
| // with the policy permissions, since they apply to the same device and |
| // URL. |
| { |
| base::RunLoop loop; |
| auto barrier_closure = base::BarrierClosure(5, loop.QuitClosure()); |
| EXPECT_CALL(observer_, OnObjectPermissionChanged( |
| {ContentSettingsType::HID_GUARD}, |
| ContentSettingsType::HID_CHOOSER_DATA)) |
| .Times(5) |
| .WillRepeatedly(RunClosure(barrier_closure)); |
| auto* hid_chooser_context = |
| HidChooserContextFactory::GetForProfile(profile()); |
| hid_chooser_context->GrantDevicePermission(kChromiumOrigin, |
| *persistent_device_); |
| hid_chooser_context->GrantDevicePermission(kGoogleOrigin, |
| *persistent_device_); |
| hid_chooser_context->GrantDevicePermission(kWebUIOrigin, |
| *persistent_device_); |
| hid_chooser_context->GrantDevicePermission(kAndroidOrigin, |
| *ephemeral_device_); |
| hid_chooser_context->GrantDevicePermission(kAndroidOrigin, |
| *user_granted_device_); |
| loop.Run(); |
| } |
| |
| if (off_the_record_device_) { |
| base::RunLoop loop; |
| EXPECT_CALL(observer_, OnObjectPermissionChanged( |
| {ContentSettingsType::HID_GUARD}, |
| ContentSettingsType::HID_CHOOSER_DATA)) |
| .WillOnce(RunClosure(loop.QuitClosure())); |
| HidChooserContextFactory::GetForProfile(incognito_profile()) |
| ->GrantDevicePermission(kChromiumOrigin, *off_the_record_device_); |
| loop.Run(); |
| } |
| } |
| |
| void SetUpUserGrantedPermissionForOrigin(const url::Origin& origin) override { |
| auto* hid_chooser_context = |
| HidChooserContextFactory::GetForProfile(profile()); |
| base::RunLoop loop; |
| EXPECT_CALL(observer_, OnObjectPermissionChanged( |
| {ContentSettingsType::HID_GUARD}, |
| ContentSettingsType::HID_CHOOSER_DATA)) |
| .WillOnce(RunClosure(loop.QuitClosure())); |
| hid_chooser_context->GrantDevicePermission(origin, *user_granted_device_); |
| loop.Run(); |
| } |
| |
| base::Value GetPersistentDeviceValueForOrigin( |
| const url::Origin& origin) override { |
| return base::Value( |
| HidChooserContext::DeviceInfoToValue(*persistent_device_)); |
| } |
| |
| base::Value GetUserGrantedDeviceValueForOrigin( |
| const url::Origin& origin) override { |
| return base::Value( |
| HidChooserContext::DeviceInfoToValue(*user_granted_device_)); |
| } |
| |
| std::string GetAllDevicesDisplayName() override { return "Any HID device"; } |
| |
| std::string GetDevicesFromGoogleDisplayName() override { |
| return "HID devices from vendor 18D1"; |
| } |
| |
| std::string GetDevicesFromVendor18D2DisplayName() override { |
| return "HID devices from vendor 18D2"; |
| } |
| |
| std::string GetUnknownProductDisplayName() override { |
| return "HID device (18D1:162E)"; |
| } |
| |
| permissions::ObjectPermissionContextBase* GetChooserContext( |
| Profile* profile) override { |
| return HidChooserContextFactory::GetForProfile(profile); |
| } |
| |
| ContentSettingsType content_type() override { |
| return ContentSettingsType::HID_CHOOSER_DATA; |
| } |
| |
| ContentSettingsType guard_type() override { |
| return ContentSettingsType::HID_GUARD; |
| } |
| |
| device::FakeHidManager hid_manager_; |
| device::mojom::HidDeviceInfoPtr ephemeral_device_; |
| device::mojom::HidDeviceInfoPtr off_the_record_device_; |
| device::mojom::HidDeviceInfoPtr persistent_device_; |
| device::mojom::HidDeviceInfoPtr user_granted_device_; |
| }; |
| |
| TEST_F(SiteSettingsHandlerHidTest, HandleGetChooserExceptionList) { |
| TestHandleGetChooserExceptionList(); |
| } |
| |
| TEST_F(SiteSettingsHandlerHidTest, |
| HandleGetChooserExceptionListForOffTheRecord) { |
| TestHandleGetChooserExceptionListForOffTheRecord(); |
| } |
| |
| TEST_F(SiteSettingsHandlerHidTest, HandleResetChooserExceptionForSite) { |
| TestHandleResetChooserExceptionForSite(); |
| } |
| |
| TEST_F(SiteSettingsHandlerHidTest, HandleSetOriginPermissions) { |
| TestHandleSetOriginPermissions(); |
| } |
| |
| TEST_F(SiteSettingsHandlerHidTest, HandleSetOriginPermissionsPolicyOnly) { |
| TestHandleSetOriginPermissionsPolicyOnly(); |
| } |
| |
| class SiteSettingsHandlerSerialTest |
| : public SiteSettingsHandlerChooserExceptionTest { |
| protected: |
| void SetUpChooserContext() override { |
| mojo::PendingRemote<device::mojom::SerialPortManager> serial_port_manager; |
| serial_port_manager_.AddReceiver( |
| serial_port_manager.InitWithNewPipeAndPassReceiver()); |
| SerialChooserContext* serial_chooser_context = |
| SerialChooserContextFactory::GetForProfile(profile()); |
| serial_chooser_context->SetPortManagerForTesting( |
| std::move(serial_port_manager)); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void SetUpPolicyGrantedPermissions() override { |
| auto* local_state = TestingBrowserProcess::GetGlobal()->local_state(); |
| ASSERT_TRUE(local_state); |
| local_state->Set(prefs::kManagedSerialAllowUsbDevicesForUrls, ParseJson(R"( |
| [ |
| { |
| "devices": [{ "vendor_id": 6353, "product_id": 5678 }], |
| "urls": ["https://chromium.org"] |
| }, { |
| "devices": [{ "vendor_id": 6353 }], |
| "urls": ["https://android.com"] |
| }, { |
| "devices": [{ "vendor_id": 6354 }], |
| "urls": ["https://android.com"] |
| } |
| ])")); |
| local_state->Set(prefs::kManagedSerialAllowAllPortsForUrls, |
| ParseJson(R"([ "https://google.com" ])")); |
| } |
| |
| void SetUpOffTheRecordChooserContext() override { |
| CreateIncognitoProfile(); |
| mojo::PendingRemote<device::mojom::SerialPortManager> serial_port_manager; |
| serial_port_manager_.AddReceiver( |
| serial_port_manager.InitWithNewPipeAndPassReceiver()); |
| SerialChooserContext* serial_chooser_context = |
| SerialChooserContextFactory::GetForProfile(incognito_profile()); |
| serial_chooser_context->SetPortManagerForTesting( |
| std::move(serial_port_manager)); |
| base::RunLoop().RunUntilIdle(); |
| GetChooserContext(incognito_profile())->AddObserver(&observer_); |
| } |
| |
| void AddPersistentDevice() override { |
| persistent_port_ = device::mojom::SerialPortInfo::New(); |
| persistent_port_->token = base::UnguessableToken::Create(); |
| persistent_port_->display_name = "persistent-device"; |
| #if BUILDFLAG(IS_WIN) |
| persistent_port_->device_instance_id = "1"; |
| #else |
| persistent_port_->has_vendor_id = true; |
| persistent_port_->vendor_id = 6353; |
| persistent_port_->has_product_id = true; |
| persistent_port_->product_id = 5678; |
| persistent_port_->serial_number = "123ABC"; |
| #if BUILDFLAG(IS_MAC) |
| persistent_port_->usb_driver_name = "AppleUSBCDC"; |
| #endif |
| #endif // BUILDFLAG(IS_WIN) |
| serial_port_manager_.AddPort(persistent_port_.Clone()); |
| } |
| |
| void AddEphemeralDevice() override { |
| ephemeral_port_ = device::mojom::SerialPortInfo::New(); |
| ephemeral_port_->token = base::UnguessableToken::Create(); |
| ephemeral_port_->display_name = "ephemeral-device"; |
| #if BUILDFLAG(IS_WIN) |
| ephemeral_port_->device_instance_id = "2"; |
| #else |
| ephemeral_port_->has_vendor_id = true; |
| ephemeral_port_->vendor_id = 6354; |
| ephemeral_port_->has_product_id = true; |
| ephemeral_port_->product_id = 0; |
| #if BUILDFLAG(IS_MAC) |
| ephemeral_port_->usb_driver_name = "AppleUSBCDC"; |
| #endif |
| #endif // BUILDFLAG(IS_WIN) |
| serial_port_manager_.AddPort(ephemeral_port_.Clone()); |
| } |
| |
| void AddUserGrantedDevice() override { |
| user_granted_port_ = device::mojom::SerialPortInfo::New(); |
| user_granted_port_->token = base::UnguessableToken::Create(); |
| user_granted_port_->display_name = "user-granted-device"; |
| #if BUILDFLAG(IS_WIN) |
| user_granted_port_->device_instance_id = "3"; |
| #else |
| user_granted_port_->has_vendor_id = true; |
| user_granted_port_->vendor_id = 6355; |
| user_granted_port_->has_product_id = true; |
| user_granted_port_->product_id = 0; |
| user_granted_port_->serial_number = "789XYZ"; |
| #if BUILDFLAG(IS_MAC) |
| user_granted_port_->usb_driver_name = "AppleUSBCDC"; |
| #endif |
| #endif // BUILDFLAG(IS_WIN) |
| serial_port_manager_.AddPort(user_granted_port_.Clone()); |
| } |
| |
| void AddOffTheRecordDevice() override { |
| off_the_record_port_ = device::mojom::SerialPortInfo::New(); |
| off_the_record_port_->token = base::UnguessableToken::Create(); |
| off_the_record_port_->display_name = "off-the-record-device"; |
| #if BUILDFLAG(IS_WIN) |
| off_the_record_port_->device_instance_id = "4"; |
| #else |
| off_the_record_port_->has_vendor_id = true; |
| off_the_record_port_->vendor_id = 6353; |
| off_the_record_port_->has_product_id = true; |
| off_the_record_port_->product_id = 8765; |
| off_the_record_port_->serial_number = "A9B8C7"; |
| #if BUILDFLAG(IS_MAC) |
| off_the_record_port_->usb_driver_name = "AppleUSBCDC"; |
| #endif |
| #endif // BUILDFLAG(IS_WIN) |
| serial_port_manager_.AddPort(off_the_record_port_.Clone()); |
| } |
| |
| void SetUpUserGrantedPermissions() override { |
| const auto kAndroidOrigin = url::Origin::Create(kAndroidUrl); |
| const auto kChromiumOrigin = url::Origin::Create(kChromiumUrl); |
| const auto kGoogleOrigin = url::Origin::Create(kGoogleUrl); |
| const auto kWebUIOrigin = url::Origin::Create(kWebUIUrl); |
| |
| // Add the user granted permissions for testing. |
| // These two persistent device permissions should be lumped together |
| // with the policy permissions, since they apply to the same device and |
| // URL. |
| { |
| base::RunLoop loop; |
| auto barrier_closure = base::BarrierClosure(5, loop.QuitClosure()); |
| EXPECT_CALL(observer_, OnObjectPermissionChanged( |
| {ContentSettingsType::SERIAL_GUARD}, |
| ContentSettingsType::SERIAL_CHOOSER_DATA)) |
| .Times(5) |
| .WillRepeatedly(RunClosure(barrier_closure)); |
| auto* serial_chooser_context = |
| SerialChooserContextFactory::GetForProfile(profile()); |
| serial_chooser_context->GrantPortPermission(kChromiumOrigin, |
| *persistent_port_); |
| serial_chooser_context->GrantPortPermission(kGoogleOrigin, |
| *persistent_port_); |
| serial_chooser_context->GrantPortPermission(kWebUIOrigin, |
| *persistent_port_); |
| serial_chooser_context->GrantPortPermission(kAndroidOrigin, |
| *ephemeral_port_); |
| serial_chooser_context->GrantPortPermission(kAndroidOrigin, |
| *user_granted_port_); |
| loop.Run(); |
| } |
| |
| if (off_the_record_port_) { |
| base::RunLoop loop; |
| EXPECT_CALL(observer_, OnObjectPermissionChanged( |
| {ContentSettingsType::SERIAL_GUARD}, |
| ContentSettingsType::SERIAL_CHOOSER_DATA)) |
| .WillOnce(RunClosure(loop.QuitClosure())); |
| SerialChooserContextFactory::GetForProfile(incognito_profile()) |
| ->GrantPortPermission(kChromiumOrigin, *off_the_record_port_); |
| loop.Run(); |
| } |
| } |
| |
| void SetUpUserGrantedPermissionForOrigin(const url::Origin& origin) override { |
| auto* serial_chooser_context = |
| SerialChooserContextFactory::GetForProfile(profile()); |
| base::RunLoop loop; |
| EXPECT_CALL(observer_, OnObjectPermissionChanged( |
| {ContentSettingsType::SERIAL_GUARD}, |
| ContentSettingsType::SERIAL_CHOOSER_DATA)) |
| .WillOnce(RunClosure(loop.QuitClosure())); |
| serial_chooser_context->GrantPortPermission(origin, *user_granted_port_); |
| loop.Run(); |
| } |
| |
| base::Value GetPersistentDeviceValueForOrigin( |
| const url::Origin& origin) override { |
| return base::Value( |
| SerialChooserContext::PortInfoToValue(*persistent_port_)); |
| } |
| |
| base::Value GetUserGrantedDeviceValueForOrigin( |
| const url::Origin& origin) override { |
| return base::Value( |
| SerialChooserContext::PortInfoToValue(*user_granted_port_)); |
| } |
| |
| std::string GetAllDevicesDisplayName() override { return "Any serial port"; } |
| |
| std::string GetDevicesFromGoogleDisplayName() override { |
| return "USB devices from Google Inc."; |
| } |
| |
| std::string GetDevicesFromVendor18D2DisplayName() override { |
| return "USB devices from vendor 18D2"; |
| } |
| |
| std::string GetUnknownProductDisplayName() override { |
| return "USB device from Google Inc. (product 162E)"; |
| } |
| |
| permissions::ObjectPermissionContextBase* GetChooserContext( |
| Profile* profile) override { |
| return SerialChooserContextFactory::GetForProfile(profile); |
| } |
| |
| ContentSettingsType content_type() override { |
| return ContentSettingsType::SERIAL_CHOOSER_DATA; |
| } |
| |
| ContentSettingsType guard_type() override { |
| return ContentSettingsType::SERIAL_GUARD; |
| } |
| |
| device::FakeSerialPortManager serial_port_manager_; |
| device::mojom::SerialPortInfoPtr ephemeral_port_; |
| device::mojom::SerialPortInfoPtr off_the_record_port_; |
| device::mojom::SerialPortInfoPtr persistent_port_; |
| device::mojom::SerialPortInfoPtr user_granted_port_; |
| }; |
| |
| TEST_F(SiteSettingsHandlerSerialTest, HandleGetChooserExceptionList) { |
| TestHandleGetChooserExceptionList(); |
| } |
| |
| TEST_F(SiteSettingsHandlerSerialTest, |
| HandleGetChooserExceptionListForOffTheRecord) { |
| TestHandleGetChooserExceptionListForOffTheRecord(); |
| } |
| |
| TEST_F(SiteSettingsHandlerSerialTest, HandleResetChooserExceptionForSite) { |
| TestHandleResetChooserExceptionForSite(); |
| } |
| |
| TEST_F(SiteSettingsHandlerSerialTest, HandleSetOriginPermissions) { |
| TestHandleSetOriginPermissions(); |
| } |
| |
| TEST_F(SiteSettingsHandlerSerialTest, HandleSetOriginPermissionsPolicyOnly) { |
| TestHandleSetOriginPermissionsPolicyOnly(); |
| } |
| #endif // !BUILDFLAG(IS_ANDROID) |
| |
| class SiteSettingsHandlerUsbTest |
| : public SiteSettingsHandlerChooserExceptionTest { |
| protected: |
| void SetUpChooserContext() override { |
| mojo::PendingRemote<device::mojom::UsbDeviceManager> device_manager; |
| usb_device_manager_.AddReceiver( |
| device_manager.InitWithNewPipeAndPassReceiver()); |
| UsbChooserContext* usb_chooser_context = |
| UsbChooserContextFactory::GetForProfile(profile()); |
| usb_chooser_context->SetDeviceManagerForTesting(std::move(device_manager)); |
| TestFuture<std::vector<device::mojom::UsbDeviceInfoPtr>> get_devices_future; |
| usb_chooser_context->GetDevices(get_devices_future.GetCallback()); |
| EXPECT_TRUE(get_devices_future.Wait()); |
| } |
| |
| void SetUpPolicyGrantedPermissions() override { |
| profile()->GetPrefs()->Set(prefs::kManagedWebUsbAllowDevicesForUrls, |
| ParseJson(R"( |
| [ |
| { |
| "devices": [{ "vendor_id": 6353, "product_id": 5678 }], |
| "urls": ["https://chromium.org"] |
| }, { |
| "devices": [{ "vendor_id": 6353 }], |
| "urls": ["https://google.com,https://android.com"] |
| }, { |
| "devices": [{ "vendor_id": 6354 }], |
| "urls": ["https://android.com,"] |
| }, { |
| "devices": [{}], |
| "urls": ["https://google.com,https://google.com"] |
| } |
| ])")); |
| } |
| |
| void SetUpOffTheRecordChooserContext() override { |
| CreateIncognitoProfile(); |
| mojo::PendingRemote<device::mojom::UsbDeviceManager> device_manager; |
| usb_device_manager_.AddReceiver( |
| device_manager.InitWithNewPipeAndPassReceiver()); |
| UsbChooserContext* usb_chooser_context = |
| UsbChooserContextFactory::GetForProfile(incognito_profile()); |
| usb_chooser_context->SetDeviceManagerForTesting(std::move(device_manager)); |
| TestFuture<std::vector<device::mojom::UsbDeviceInfoPtr>> get_devices_future; |
| usb_chooser_context->GetDevices(get_devices_future.GetCallback()); |
| EXPECT_TRUE(get_devices_future.Wait()); |
| GetChooserContext(incognito_profile())->AddObserver(&observer_); |
| } |
| |
| void AddPersistentDevice() override { |
| persistent_device_ = usb_device_manager_.CreateAndAddDevice( |
| /*vendor_id=*/6353, /*product_id=*/5678, |
| /*manufacturer_string=*/"Google", |
| /*product_string=*/"persistent-device", /*serial_number=*/"123ABC"); |
| } |
| |
| void AddEphemeralDevice() override { |
| ephemeral_device_ = usb_device_manager_.CreateAndAddDevice( |
| /*vendor_id=*/6354, /*product_id=*/0, |
| /*manufacturer_string=*/"Google", |
| /*product_string=*/"ephemeral-device", /*serial_number=*/""); |
| } |
| |
| void AddUserGrantedDevice() override { |
| user_granted_device_ = usb_device_manager_.CreateAndAddDevice( |
| /*vendor_id=*/6355, /*product_id=*/0, |
| /*manufacturer_string=*/"Google", |
| /*product_string=*/"user-granted-device", |
| /*serial_number=*/"789XYZ"); |
| } |
| |
| void AddOffTheRecordDevice() override { |
| off_the_record_device_ = usb_device_manager_.CreateAndAddDevice( |
| /*vendor_id=*/6353, /*product_id=*/8765, |
| /*manufacturer_string=*/"Google", |
| /*product_string=*/"off-the-record-device", |
| /*serial_number=*/"A9B8C7"); |
| } |
| |
| void SetUpUserGrantedPermissions() override { |
| const auto kAndroidOrigin = url::Origin::Create(kAndroidUrl); |
| const auto kChromiumOrigin = url::Origin::Create(kChromiumUrl); |
| const auto kGoogleOrigin = url::Origin::Create(kGoogleUrl); |
| const auto kWebUIOrigin = url::Origin::Create(kWebUIUrl); |
| |
| // Add the user granted permissions for testing. |
| // These two persistent device permissions should be lumped together |
| // with the policy permissions, since they apply to the same device and |
| // URL. |
| { |
| base::RunLoop loop; |
| auto barrier_closure = base::BarrierClosure(5, loop.QuitClosure()); |
| EXPECT_CALL(observer_, OnObjectPermissionChanged( |
| {ContentSettingsType::USB_GUARD}, |
| ContentSettingsType::USB_CHOOSER_DATA)) |
| .Times(5) |
| .WillRepeatedly(RunClosure(barrier_closure)); |
| auto* usb_chooser_context = |
| UsbChooserContextFactory::GetForProfile(profile()); |
| usb_chooser_context->GrantDevicePermission(kChromiumOrigin, |
| *persistent_device_); |
| usb_chooser_context->GrantDevicePermission(kGoogleOrigin, |
| *persistent_device_); |
| usb_chooser_context->GrantDevicePermission(kWebUIOrigin, |
| *persistent_device_); |
| usb_chooser_context->GrantDevicePermission(kAndroidOrigin, |
| *ephemeral_device_); |
| usb_chooser_context->GrantDevicePermission(kAndroidOrigin, |
| *user_granted_device_); |
| loop.Run(); |
| } |
| |
| if (off_the_record_device_) { |
| base::RunLoop loop; |
| EXPECT_CALL(observer_, OnObjectPermissionChanged( |
| {ContentSettingsType::USB_GUARD}, |
| ContentSettingsType::USB_CHOOSER_DATA)) |
| .WillOnce(RunClosure(loop.QuitClosure())); |
| UsbChooserContextFactory::GetForProfile(incognito_profile()) |
| ->GrantDevicePermission(kChromiumOrigin, *off_the_record_device_); |
| loop.Run(); |
| } |
| } |
| |
| void SetUpUserGrantedPermissionForOrigin(const url::Origin& origin) override { |
| auto* usb_chooser_context = |
| UsbChooserContextFactory::GetForProfile(profile()); |
| base::RunLoop loop; |
| EXPECT_CALL(observer_, OnObjectPermissionChanged( |
| {ContentSettingsType::USB_GUARD}, |
| ContentSettingsType::USB_CHOOSER_DATA)) |
| .WillOnce(RunClosure(loop.QuitClosure())); |
| usb_chooser_context->GrantDevicePermission(origin, *user_granted_device_); |
| loop.Run(); |
| } |
| |
| base::Value GetPersistentDeviceValueForOrigin( |
| const url::Origin& origin) override { |
| return base::Value( |
| UsbChooserContext::DeviceInfoToValue(*persistent_device_)); |
| } |
| |
| base::Value GetUserGrantedDeviceValueForOrigin( |
| const url::Origin& origin) override { |
| return base::Value( |
| UsbChooserContext::DeviceInfoToValue(*user_granted_device_)); |
| } |
| |
| std::string GetAllDevicesDisplayName() override { |
| return "Devices from any vendor"; |
| } |
| |
| std::string GetDevicesFromGoogleDisplayName() override { |
| return "Devices from Google Inc."; |
| } |
| |
| std::string GetDevicesFromVendor18D2DisplayName() override { |
| return "Devices from vendor 0x18D2"; |
| } |
| |
| std::string GetUnknownProductDisplayName() override { |
| return "Unknown product 0x162E from Google Inc."; |
| } |
| |
| permissions::ObjectPermissionContextBase* GetChooserContext( |
| Profile* profile) override { |
| return UsbChooserContextFactory::GetForProfile(profile); |
| } |
| |
| ContentSettingsType content_type() override { |
| return ContentSettingsType::USB_CHOOSER_DATA; |
| } |
| |
| ContentSettingsType guard_type() override { |
| return ContentSettingsType::USB_GUARD; |
| } |
| |
| device::FakeUsbDeviceManager usb_device_manager_; |
| device::mojom::UsbDeviceInfoPtr ephemeral_device_; |
| device::mojom::UsbDeviceInfoPtr off_the_record_device_; |
| device::mojom::UsbDeviceInfoPtr persistent_device_; |
| device::mojom::UsbDeviceInfoPtr user_granted_device_; |
| }; |
| |
| TEST_F(SiteSettingsHandlerUsbTest, HandleGetChooserExceptionList) { |
| TestHandleGetChooserExceptionList(); |
| } |
| |
| TEST_F(SiteSettingsHandlerUsbTest, |
| HandleGetChooserExceptionListForOffTheRecord) { |
| TestHandleGetChooserExceptionListForOffTheRecord(); |
| } |
| |
| TEST_F(SiteSettingsHandlerUsbTest, HandleResetChooserExceptionForSite) { |
| TestHandleResetChooserExceptionForSite(); |
| } |
| |
| TEST_F(SiteSettingsHandlerUsbTest, HandleSetOriginPermissions) { |
| TestHandleSetOriginPermissions(); |
| } |
| |
| TEST_F(SiteSettingsHandlerUsbTest, HandleSetOriginPermissionsPolicyOnly) { |
| TestHandleSetOriginPermissionsPolicyOnly(); |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, HandleClearEtldPlus1DataAndCookies) { |
| SetupModels(); |
| |
| EXPECT_EQ(28u, |
| handler()->cookies_tree_model_->GetRoot()->GetTotalNodeCount()); |
| |
| auto verify_site_group = [](const base::Value& site_group, |
| std::string expected_etld_plus1) { |
| ASSERT_TRUE(site_group.is_dict()); |
| const std::string* etld_plus1 = |
| site_group.GetDict().FindString("etldPlus1"); |
| ASSERT_TRUE(etld_plus1); |
| ASSERT_EQ(expected_etld_plus1, *etld_plus1); |
| }; |
| |
| base::Value::List storage_and_cookie_list = GetOnStorageFetchedSentList(); |
| EXPECT_EQ(4U, storage_and_cookie_list.size()); |
| verify_site_group(storage_and_cookie_list[0], "example.com"); |
| |
| base::Value::List args; |
| args.Append("example.com"); |
| handler()->HandleClearEtldPlus1DataAndCookies(args); |
| |
| // All host nodes for non-secure example.com, and abc.example.com, which do |
| // not have any unpartitioned storage, should have been removed. |
| ASSERT_EQ(0u, GetHostNodes(GURL("http://example.com")).size()); |
| ASSERT_EQ(0u, GetHostNodes(GURL("http://abc.example.com")).size()); |
| |
| // Confirm that partitioned cookies for www.example.com have not been deleted, |
| auto remaining_host_nodes = GetHostNodes(GURL("https://www.example.com")); |
| |
| // example.com storage partitioned on other sites should still remain. |
| { |
| ASSERT_EQ(1u, remaining_host_nodes.size()); |
| ASSERT_EQ(1u, remaining_host_nodes[0]->children().size()); |
| const auto& storage_node = remaining_host_nodes[0]->children()[0]; |
| ASSERT_EQ(CookieTreeNode::DetailedInfo::TYPE_COOKIES, |
| storage_node->GetDetailedInfo().node_type); |
| ASSERT_EQ(2u, storage_node->children().size()); |
| for (const auto& cookie_node : storage_node->children()) { |
| const auto& cookie = cookie_node->GetDetailedInfo().cookie; |
| EXPECT_EQ("www.example.com", cookie->Domain()); |
| EXPECT_TRUE(cookie->IsPartitioned()); |
| } |
| } |
| |
| EXPECT_EQ(19u, |
| handler()->cookies_tree_model_->GetRoot()->GetTotalNodeCount()); |
| |
| storage_and_cookie_list = GetOnStorageFetchedSentList(); |
| EXPECT_EQ(3U, storage_and_cookie_list.size()); |
| verify_site_group(storage_and_cookie_list[0], "google.com"); |
| |
| args.clear(); |
| args.Append("google.com"); |
| |
| handler()->HandleClearEtldPlus1DataAndCookies(args); |
| |
| EXPECT_EQ(14u, |
| handler()->cookies_tree_model_->GetRoot()->GetTotalNodeCount()); |
| |
| storage_and_cookie_list = GetOnStorageFetchedSentList(); |
| EXPECT_EQ(2U, storage_and_cookie_list.size()); |
| verify_site_group(storage_and_cookie_list[0], "google.com.au"); |
| |
| args.clear(); |
| args.Append("google.com.au"); |
| |
| handler()->HandleClearEtldPlus1DataAndCookies(args); |
| // No nodes representing storage partitioned on google.com.au should be |
| // present. |
| for (const auto& host_node : |
| handler()->cookies_tree_model_->GetRoot()->children()) { |
| for (const auto& storage_node : host_node->children()) { |
| if (storage_node->GetDetailedInfo().node_type != |
| CookieTreeNode::DetailedInfo::TYPE_COOKIES) { |
| continue; |
| } |
| for (const auto& cookie_node : storage_node->children()) { |
| const auto& cookie = cookie_node->GetDetailedInfo().cookie; |
| if (cookie->IsPartitioned()) { |
| EXPECT_NE("google.com.au", |
| cookie->PartitionKey()->site().GetURL().host()); |
| } |
| } |
| } |
| } |
| |
| storage_and_cookie_list = GetOnStorageFetchedSentList(); |
| EXPECT_EQ(1U, storage_and_cookie_list.size()); |
| verify_site_group(storage_and_cookie_list[0], "ungrouped.com"); |
| |
| args.clear(); |
| args.Append("ungrouped.com"); |
| |
| handler()->HandleClearEtldPlus1DataAndCookies(args); |
| |
| storage_and_cookie_list = GetOnStorageFetchedSentList(); |
| EXPECT_EQ(0U, storage_and_cookie_list.size()); |
| } |
| |
| TEST_P(SiteSettingsHandlerTest, HandleClearUnpartitionedUsage) { |
| SetupModels(); |
| |
| EXPECT_EQ(28u, |
| handler()->cookies_tree_model_->GetRoot()->GetTotalNodeCount()); |
| EXPECT_EQ(1, std::distance(handler()->browsing_data_model_->begin(), |
| handler()->browsing_data_model_->end())); |
| |
| base::Value::List args; |
| args.Append(GetParam() ? "https://www.example.com/" |
| : "http://www.example.com/"); |
| handler()->HandleClearUnpartitionedUsage(args); |
| |
| EXPECT_EQ(1, std::distance(handler()->browsing_data_model_->begin(), |
| handler()->browsing_data_model_->end())); |
| |
| // Confirm that only the unpartitioned items for example.com have been |
| // cleared. |
| auto remaining_host_nodes = GetHostNodes(GURL("https://www.example.com")); |
| |
| // There should only be partitioned cookie entries remaining for the site. |
| ASSERT_EQ(1u, remaining_host_nodes.size()); |
| ASSERT_EQ(1u, remaining_host_nodes[0]->children().size()); |
| const auto& storage_node = remaining_host_nodes[0]->children()[0]; |
| ASSERT_EQ(CookieTreeNode::DetailedInfo::TYPE_COOKIES, |
| storage_node->GetDetailedInfo().node_type); |
| ASSERT_EQ(2u, storage_node->children().size()); |
| for (const auto& cookie_node : storage_node->children()) { |
| const auto& cookie = cookie_node->GetDetailedInfo().cookie; |
| EXPECT_EQ("www.example.com", cookie->Domain()); |
| EXPECT_TRUE(cookie->IsPartitioned()); |
| } |
| |
| // Partitioned storage, even when keyed on the cookie domain site, should |
| // not be cleared. |
| args = base::Value::List(); |
| args.Append("https://google.com.au/"); |
| handler()->HandleClearUnpartitionedUsage(args); |
| |
| remaining_host_nodes = GetHostNodes(GURL("https://google.com.au")); |
| |
| // A single partitioned cookie should remain. |
| ASSERT_EQ(1u, remaining_host_nodes.size()); |
| ASSERT_EQ(1u, remaining_host_nodes[0]->children().size()); |
| const auto& cookies_node = remaining_host_nodes[0]->children()[0]; |
| ASSERT_EQ(1u, cookies_node->children().size()); |
| const auto& cookie_node = cookies_node->children()[0]; |
| const auto& cookie = cookie_node->GetDetailedInfo().cookie; |
| EXPECT_TRUE(cookie->IsPartitioned()); |
| |
| args = base::Value::List(); |
| args.Append("https://www.google.com/"); |
| handler()->HandleClearUnpartitionedUsage(args); |
| |
| EXPECT_EQ(0, std::distance(handler()->browsing_data_model_->begin(), |
| handler()->browsing_data_model_->end())); |
| |
| // Clearing Site Specific Media Licenses Tests |
| #if BUILDFLAG(IS_WIN) |
| PrefService* user_prefs = profile()->GetPrefs(); |
| |
| // In the beginning, there should be nothing stored in the origin data. |
| ASSERT_EQ(0u, user_prefs->GetDict(prefs::kMediaCdmOriginData).size()); |
| |
| auto entry_google = base::Value::Dict().Set( |
| "https://www.google.com/", |
| base::UnguessableTokenToValue(base::UnguessableToken::Create())); |
| |
| base::Value::Dict entry_example; |
| entry_example.Set( |
| "https://www.example.com/", |
| base::UnguessableTokenToValue(base::UnguessableToken::Create())); |
| |
| { |
| ScopedDictPrefUpdate update(user_prefs, prefs::kMediaCdmOriginData); |
| |
| base::Value::Dict& dict = update.Get(); |
| dict.Set("https://www.google.com/", std::move(entry_google)); |
| dict.Set("https://www.example.com/", std::move(entry_example)); |
| } |
| // The code above adds origin data for both google and example.com |
| EXPECT_EQ(2u, user_prefs->GetDict(prefs::kMediaCdmOriginData).size()); |
| |
| args = base::Value::List(); |
| args.Append("https://www.google.com/"); |
| handler()->HandleClearUnpartitionedUsage(args); |
| |
| // The code clears the origin data for just google.com, so there should still |
| // be the origin data for example.com left. |
| EXPECT_EQ(1u, user_prefs->GetDict(prefs::kMediaCdmOriginData).size()); |
| EXPECT_TRUE(user_prefs->GetDict(prefs::kMediaCdmOriginData) |
| .contains("https://www.example.com/")); |
| |
| #endif // BUILDFLAG(IS_WIN) |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, ClearClientHints) { |
| // Confirm that when the user clears unpartitioned storage, or the eTLD+1 |
| // group, client hints are also cleared. |
| SetupModels(); |
| handler()->OnStorageFetched(); |
| |
| GURL hosts[] = {GURL("https://example.com/"), GURL("https://www.example.com"), |
| GURL("https://google.com/"), GURL("https://www.google.com/")}; |
| |
| HostContentSettingsMap* host_content_settings_map = |
| HostContentSettingsMapFactory::GetForProfile(profile()); |
| ContentSettingsForOneType client_hints_settings; |
| |
| // Add setting for the two hosts host[0], host[1]. |
| base::Value client_hint_platform_version(14); |
| base::Value client_hint_bitness(16); |
| |
| base::Value::List client_hints_list; |
| client_hints_list.Append(std::move(client_hint_platform_version)); |
| client_hints_list.Append(std::move(client_hint_bitness)); |
| |
| base::Value::Dict client_hints_dictionary; |
| client_hints_dictionary.Set(client_hints::kClientHintsSettingKey, |
| std::move(client_hints_list)); |
| |
| // Add setting for the hosts. |
| for (const auto& host : hosts) { |
| host_content_settings_map->SetWebsiteSettingDefaultScope( |
| host, GURL(), ContentSettingsType::CLIENT_HINTS, |
| base::Value(client_hints_dictionary.Clone())); |
| } |
| |
| // Clear at the eTLD+1 level and ensure affected origins are cleared. |
| base::Value::List args; |
| args.Append("example.com"); |
| handler()->HandleClearEtldPlus1DataAndCookies(args); |
| host_content_settings_map->GetSettingsForOneType( |
| ContentSettingsType::CLIENT_HINTS, &client_hints_settings); |
| EXPECT_EQ(2U, client_hints_settings.size()); |
| |
| EXPECT_EQ(ContentSettingsPattern::FromURLNoWildcard(hosts[2]), |
| client_hints_settings.at(0).primary_pattern); |
| EXPECT_EQ(ContentSettingsPattern::Wildcard(), |
| client_hints_settings.at(0).secondary_pattern); |
| EXPECT_EQ(client_hints_dictionary, client_hints_settings.at(0).setting_value); |
| |
| EXPECT_EQ(ContentSettingsPattern::FromURLNoWildcard(hosts[3]), |
| client_hints_settings.at(1).primary_pattern); |
| EXPECT_EQ(ContentSettingsPattern::Wildcard(), |
| client_hints_settings.at(1).secondary_pattern); |
| EXPECT_EQ(client_hints_dictionary, client_hints_settings.at(1).setting_value); |
| |
| // Clear unpartitioned usage data, which should only affect the specific |
| // origin. |
| args.clear(); |
| args.Append("https://google.com/"); |
| handler()->HandleClearUnpartitionedUsage(args); |
| |
| // Validate the client hint has been cleared. |
| host_content_settings_map->GetSettingsForOneType( |
| ContentSettingsType::CLIENT_HINTS, &client_hints_settings); |
| EXPECT_EQ(1U, client_hints_settings.size()); |
| |
| // www.google.com should be the only remaining entry. |
| EXPECT_EQ(ContentSettingsPattern::FromURLNoWildcard(hosts[3]), |
| client_hints_settings.at(0).primary_pattern); |
| EXPECT_EQ(ContentSettingsPattern::Wildcard(), |
| client_hints_settings.at(0).secondary_pattern); |
| EXPECT_EQ(client_hints_dictionary, client_hints_settings.at(0).setting_value); |
| |
| // Clear unpartitioned usage data through HTTPS scheme, make sure https site |
| // client hints have been cleared when the specific origin HTTPS scheme |
| // exist. |
| args.clear(); |
| args.Append("http://www.google.com/"); |
| handler()->HandleClearUnpartitionedUsage(args); |
| |
| // Validate the client hint has been cleared. |
| host_content_settings_map->GetSettingsForOneType( |
| ContentSettingsType::CLIENT_HINTS, &client_hints_settings); |
| EXPECT_EQ(0U, client_hints_settings.size()); |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, ClearReducedAcceptLanguage) { |
| // Confirm that when the user clears unpartitioned storage, or the eTLD+1 |
| // group, reduce accept language are also cleared. |
| SetupModels(); |
| handler()->OnStorageFetched(); |
| |
| GURL hosts[] = {GURL("https://example.com/"), GURL("https://www.example.com"), |
| GURL("https://google.com/"), GURL("https://www.google.com/")}; |
| |
| HostContentSettingsMap* host_content_settings_map = |
| HostContentSettingsMapFactory::GetForProfile(profile()); |
| ContentSettingsForOneType accept_language_settings; |
| |
| std::string language = "en-us"; |
| base::Value::Dict accept_language_dictionary; |
| accept_language_dictionary.Set("reduce-accept-language", language); |
| |
| // Add setting for the hosts. |
| for (const auto& host : hosts) { |
| host_content_settings_map->SetWebsiteSettingDefaultScope( |
| host, GURL(), ContentSettingsType::REDUCED_ACCEPT_LANGUAGE, |
| base::Value(accept_language_dictionary.Clone())); |
| } |
| |
| // Clear at the eTLD+1 level and ensure affected origins are cleared. |
| base::Value::List args; |
| args.Append("example.com"); |
| handler()->HandleClearEtldPlus1DataAndCookies(args); |
| host_content_settings_map->GetSettingsForOneType( |
| ContentSettingsType::REDUCED_ACCEPT_LANGUAGE, &accept_language_settings); |
| EXPECT_EQ(2U, accept_language_settings.size()); |
| |
| EXPECT_EQ(ContentSettingsPattern::FromURLNoWildcard(hosts[2]), |
| accept_language_settings.at(0).primary_pattern); |
| EXPECT_EQ(ContentSettingsPattern::Wildcard(), |
| accept_language_settings.at(0).secondary_pattern); |
| EXPECT_EQ(accept_language_dictionary, |
| accept_language_settings.at(0).setting_value); |
| |
| EXPECT_EQ(ContentSettingsPattern::FromURLNoWildcard(hosts[3]), |
| accept_language_settings.at(1).primary_pattern); |
| EXPECT_EQ(ContentSettingsPattern::Wildcard(), |
| accept_language_settings.at(1).secondary_pattern); |
| EXPECT_EQ(accept_language_dictionary, |
| accept_language_settings.at(1).setting_value); |
| |
| // Clear unpartitioned usage data, which should only affect the specific |
| // origin. |
| args.clear(); |
| args.Append("https://google.com/"); |
| handler()->HandleClearUnpartitionedUsage(args); |
| |
| // Validate the reduce accept language has been cleared. |
| host_content_settings_map->GetSettingsForOneType( |
| ContentSettingsType::REDUCED_ACCEPT_LANGUAGE, &accept_language_settings); |
| EXPECT_EQ(1U, accept_language_settings.size()); |
| |
| // www.google.com should be the only remaining entry. |
| EXPECT_EQ(ContentSettingsPattern::FromURLNoWildcard(hosts[3]), |
| accept_language_settings.at(0).primary_pattern); |
| EXPECT_EQ(ContentSettingsPattern::Wildcard(), |
| accept_language_settings.at(0).secondary_pattern); |
| EXPECT_EQ(accept_language_dictionary, |
| accept_language_settings.at(0).setting_value); |
| |
| // Clear unpartitioned usage data through HTTPS scheme, make sure https site |
| // reduced accept language have been cleared when the specific origin HTTPS |
| // scheme exist. |
| args.clear(); |
| args.Append("http://www.google.com/"); |
| handler()->HandleClearUnpartitionedUsage(args); |
| |
| // Validate the reduced accept language has been cleared. |
| host_content_settings_map->GetSettingsForOneType( |
| ContentSettingsType::REDUCED_ACCEPT_LANGUAGE, &accept_language_settings); |
| EXPECT_EQ(0U, accept_language_settings.size()); |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, HandleClearPartitionedUsage) { |
| // Confirm that removing unpartitioned storage correctly removes the |
| // appropriate nodes. |
| SetupModels(); |
| EXPECT_EQ(28u, |
| handler()->cookies_tree_model_->GetRoot()->GetTotalNodeCount()); |
| EXPECT_EQ(1, std::distance(handler()->browsing_data_model_->begin(), |
| handler()->browsing_data_model_->end())); |
| |
| base::Value::List args; |
| args.Append("https://www.example.com/"); |
| args.Append("google.com"); |
| handler()->HandleClearPartitionedUsage(args); |
| |
| // This should have only removed cookies for embedded.com partitioned on |
| // google.com, leaving other cookies and storage untouched. |
| auto remaining_host_nodes = GetHostNodes(GURL("https://www.example.com")); |
| ASSERT_EQ(1u, remaining_host_nodes.size()); |
| |
| // Both cookies and local storage type nodes should remain. |
| ASSERT_EQ(2u, remaining_host_nodes[0]->children().size()); |
| |
| for (const auto& storage_node : remaining_host_nodes[0]->children()) { |
| if (storage_node->GetDetailedInfo().node_type == |
| CookieTreeNode::DetailedInfo::TYPE_COOKIES) { |
| // Two cookies should remain, one unpartitioned and one partitioned on |
| // a different site. |
| ASSERT_EQ(2u, storage_node->children().size()); |
| for (const auto& cookie_node : storage_node->children()) { |
| const auto& cookie = cookie_node->GetDetailedInfo().cookie; |
| if (cookie->IsPartitioned()) |
| ASSERT_EQ("google.com.au", |
| cookie->PartitionKey()->site().GetURL().host()); |
| } |
| } else { |
| ASSERT_EQ(storage_node->GetDetailedInfo().node_type, |
| CookieTreeNode::DetailedInfo::TYPE_LOCAL_STORAGES); |
| } |
| } |
| |
| // Should not have affected the browsing data model. |
| // TODO(crbug.com/1271155): Update when partitioned storage is represented |
| // by the browsing data model. |
| EXPECT_EQ(1, std::distance(handler()->browsing_data_model_->begin(), |
| handler()->browsing_data_model_->end())); |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, CookieSettingDescription) { |
| const auto kBlocked = [](int num) { |
| return l10n_util::GetPluralStringFUTF8( |
| IDS_SETTINGS_SITE_SETTINGS_COOKIES_BLOCK, num); |
| }; |
| const auto kAllowed = [](int num) { |
| return l10n_util::GetPluralStringFUTF8( |
| IDS_SETTINGS_SITE_SETTINGS_COOKIES_ALLOW, num); |
| }; |
| const std::string kBlockThirdParty = l10n_util::GetStringUTF8( |
| IDS_SETTINGS_SITE_SETTINGS_COOKIES_BLOCK_THIRD_PARTY); |
| const std::string kBlockThirdPartyIncognito = l10n_util::GetStringUTF8( |
| IDS_SETTINGS_SITE_SETTINGS_COOKIES_BLOCK_THIRD_PARTY_INCOGNITO); |
| |
| // Enforce expected default profile setting. |
| profile()->GetPrefs()->SetInteger( |
| prefs::kCookieControlsMode, |
| static_cast<int>(content_settings::CookieControlsMode::kIncognitoOnly)); |
| auto* content_settings = |
| HostContentSettingsMapFactory::GetForProfile(profile()); |
| content_settings->SetDefaultContentSetting( |
| ContentSettingsType::COOKIES, ContentSetting::CONTENT_SETTING_ALLOW); |
| web_ui()->ClearTrackedCalls(); |
| |
| // Validate get method works. |
| base::Value::List get_args; |
| get_args.Append(kCallbackId); |
| handler()->HandleGetCookieSettingDescription(get_args); |
| const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); |
| |
| EXPECT_EQ("cr.webUIResponse", data.function_name()); |
| EXPECT_EQ(kCallbackId, data.arg1()->GetString()); |
| ASSERT_TRUE(data.arg2()->GetBool()); |
| EXPECT_EQ(kBlockThirdPartyIncognito, data.arg3()->GetString()); |
| |
| // Multiple listeners will be called when prefs and content settings are |
| // changed in this test. Increment our expected call_data index accordingly. |
| int expected_call_index = 0; |
| const int kPrefListenerIndex = 1; |
| const int kContentSettingListenerIndex = 2; |
| |
| // Check updates are working, |
| profile()->GetPrefs()->SetInteger( |
| prefs::kCookieControlsMode, |
| static_cast<int>(content_settings::CookieControlsMode::kBlockThirdParty)); |
| expected_call_index += kPrefListenerIndex; |
| ValidateCookieSettingUpdate(kBlockThirdParty, expected_call_index); |
| |
| content_settings->SetDefaultContentSetting( |
| ContentSettingsType::COOKIES, ContentSetting::CONTENT_SETTING_BLOCK); |
| expected_call_index += kContentSettingListenerIndex; |
| ValidateCookieSettingUpdate(kBlocked(0), expected_call_index); |
| |
| // Check changes which do not affect the effective cookie setting. |
| profile()->GetPrefs()->SetInteger( |
| prefs::kCookieControlsMode, |
| static_cast<int>(content_settings::CookieControlsMode::kOff)); |
| expected_call_index += kPrefListenerIndex; |
| ValidateCookieSettingUpdate(kBlocked(0), expected_call_index); |
| |
| // Set to allow and check previous changes are respected. |
| content_settings->SetDefaultContentSetting( |
| ContentSettingsType::COOKIES, ContentSetting::CONTENT_SETTING_ALLOW); |
| expected_call_index += kContentSettingListenerIndex; |
| ValidateCookieSettingUpdate(kAllowed(0), expected_call_index); |
| |
| // Confirm exceptions are counted correctly. |
| GURL url1("https://example.com"); |
| GURL url2("http://example.com"); |
| GURL url3("http://another.example.com"); |
| content_settings->SetContentSettingDefaultScope( |
| url1, url1, ContentSettingsType::COOKIES, |
| ContentSetting::CONTENT_SETTING_BLOCK); |
| expected_call_index += kContentSettingListenerIndex; |
| ValidateCookieSettingUpdate(kAllowed(1), expected_call_index); |
| |
| content_settings->SetContentSettingDefaultScope( |
| url2, url2, ContentSettingsType::COOKIES, |
| ContentSetting::CONTENT_SETTING_ALLOW); |
| expected_call_index += kContentSettingListenerIndex; |
| ValidateCookieSettingUpdate(kAllowed(1), expected_call_index); |
| |
| content_settings->SetContentSettingDefaultScope( |
| url3, url3, ContentSettingsType::COOKIES, |
| ContentSetting::CONTENT_SETTING_SESSION_ONLY); |
| expected_call_index += kContentSettingListenerIndex; |
| ValidateCookieSettingUpdate(kAllowed(1), expected_call_index); |
| |
| content_settings->SetDefaultContentSetting( |
| ContentSettingsType::COOKIES, ContentSetting::CONTENT_SETTING_BLOCK); |
| expected_call_index += kContentSettingListenerIndex; |
| ValidateCookieSettingUpdate(kBlocked(2), expected_call_index); |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, HandleGetFpsMembershipLabel) { |
| base::Value::List args; |
| args.Append("getFpsMembershipLabel"); |
| args.Append(5); |
| args.Append("google.com"); |
| handler()->HandleGetFpsMembershipLabel(args); |
| const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); |
| |
| EXPECT_EQ("cr.webUIResponse", data.function_name()); |
| EXPECT_EQ("getFpsMembershipLabel", data.arg1()->GetString()); |
| ASSERT_TRUE(data.arg2()->GetBool()); |
| EXPECT_EQ("5 sites in google.com's group", data.arg3()->GetString()); |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, HandleGetFormattedBytes) { |
| const double size = 120000000000; |
| base::Value::List get_args; |
| get_args.Append(kCallbackId); |
| get_args.Append(size); |
| handler()->HandleGetFormattedBytes(get_args); |
| |
| // Validate that this method can handle large data. |
| const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); |
| EXPECT_EQ("cr.webUIResponse", data.function_name()); |
| EXPECT_EQ(kCallbackId, data.arg1()->GetString()); |
| ASSERT_TRUE(data.arg2()->GetBool()); |
| EXPECT_EQ(base::UTF16ToUTF8(ui::FormatBytes(int64_t(size))), |
| data.arg3()->GetString()); |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, HandleGetUsageInfo) { |
| SetupDefaultFirstPartySets(mock_privacy_sandbox_service()); |
| |
| EXPECT_CALL(*mock_privacy_sandbox_service(), IsPartOfManagedFirstPartySet(_)) |
| .Times(1) |
| .WillOnce(Return(false)); |
| EXPECT_CALL( |
| *mock_privacy_sandbox_service(), |
| IsPartOfManagedFirstPartySet(ConvertEtldToSchemefulSite("example.com"))) |
| .Times(2) |
| .WillRepeatedly(Return(true)); |
| |
| // Confirm that usage info only returns unpartitioned storage. |
| SetupModels(); |
| |
| EXPECT_EQ(28u, |
| handler()->cookies_tree_model_->GetRoot()->GetTotalNodeCount()); |
| EXPECT_EQ(1, std::distance(handler()->browsing_data_model_->begin(), |
| handler()->browsing_data_model_->end())); |
| |
| base::Value::List args; |
| args.Append("http://www.example.com"); |
| handler()->HandleFetchUsageTotal(args); |
| handler()->ServicePendingRequests(); |
| ValidateUsageInfo("http://www.example.com", "2 B", "1 cookie", |
| "1 site in example.com's group", true); |
| |
| args.clear(); |
| args.Append("http://example.com"); |
| handler()->HandleFetchUsageTotal(args); |
| handler()->ServicePendingRequests(); |
| ValidateUsageInfo("http://example.com", "", "1 cookie", |
| "1 site in example.com's group", true); |
| |
| args.clear(); |
| args.Append("http://google.com"); |
| handler()->HandleFetchUsageTotal(args); |
| handler()->ServicePendingRequests(); |
| ValidateUsageInfo("http://google.com", "", "2 cookies", |
| "2 sites in google.com's group", false); |
| args.clear(); |
| args.Append("http://ungrouped.com"); |
| handler()->HandleFetchUsageTotal(args); |
| handler()->ServicePendingRequests(); |
| ValidateUsageInfo("http://ungrouped.com", "", "1 cookie", "", false); |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, NonTreeModelDeletion) { |
| // Confirm that a BrowsingDataRemover task is started to remove Privacy |
| // Sandbox APIs that are not integrated with the tree model. |
| SetupModels(); |
| |
| base::Value::List storage_and_cookie_list = GetOnStorageFetchedSentList(); |
| EXPECT_EQ(4U, storage_and_cookie_list.size()); |
| EXPECT_CALL(*mock_browsing_topics_service(), |
| ClearTopicsDataForOrigin( |
| url::Origin::Create(GURL("https://www.google.com")))); |
| EXPECT_CALL(*mock_browsing_topics_service(), |
| ClearTopicsDataForOrigin( |
| url::Origin::Create(GURL("https://google.com")))); |
| |
| base::Value::List args; |
| args.Append("google.com"); |
| handler()->HandleClearEtldPlus1DataAndCookies(args); |
| |
| auto* browsing_data_remover = profile()->GetBrowsingDataRemover(); |
| EXPECT_EQ(content::BrowsingDataRemover::DATA_TYPE_PRIVACY_SANDBOX & |
| ~content::BrowsingDataRemover::DATA_TYPE_TRUST_TOKENS, |
| browsing_data_remover->GetLastUsedRemovalMaskForTesting()); |
| EXPECT_EQ(base::Time::Min(), |
| browsing_data_remover->GetLastUsedBeginTimeForTesting()); |
| EXPECT_EQ(content::BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB, |
| browsing_data_remover->GetLastUsedOriginTypeMaskForTesting()); |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, FirstPartySetsMembership) { |
| SetupDefaultFirstPartySets(mock_privacy_sandbox_service()); |
| |
| EXPECT_CALL(*mock_privacy_sandbox_service(), IsPartOfManagedFirstPartySet(_)) |
| .Times(2) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL( |
| *mock_privacy_sandbox_service(), |
| IsPartOfManagedFirstPartySet(ConvertEtldToSchemefulSite("example.com"))) |
| .Times(1) |
| .WillOnce(Return(true)); |
| |
| SetupModels(); |
| |
| handler()->ClearAllSitesMapForTesting(); |
| |
| handler()->OnStorageFetched(); |
| const content::TestWebUI::CallData& data = *web_ui()->call_data().back(); |
| EXPECT_EQ("cr.webUIListenerCallback", data.function_name()); |
| |
| ASSERT_TRUE(data.arg1()->is_string()); |
| EXPECT_EQ("onStorageListFetched", data.arg1()->GetString()); |
| |
| ASSERT_TRUE(data.arg2()->is_list()); |
| const base::Value::List& storage_and_cookie_list = data.arg2()->GetList(); |
| EXPECT_EQ(4U, storage_and_cookie_list.size()); |
| |
| auto first_party_sets = GetTestFirstPartySets(); |
| |
| ValidateSitesWithFps(storage_and_cookie_list, first_party_sets); |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, |
| HandleIgnoreOriginsForNotificationPermissionReview) { |
| base::test::ScopedFeatureList scoped_feature; |
| scoped_feature.InitAndEnableFeature( |
| ::features::kSafetyCheckNotificationPermissions); |
| |
| HostContentSettingsMap* content_settings = |
| HostContentSettingsMapFactory::GetForProfile(profile()); |
| ContentSettingsForOneType ignored_patterns; |
| content_settings->GetSettingsForOneType( |
| ContentSettingsType::NOTIFICATION_PERMISSION_REVIEW, &ignored_patterns); |
| ASSERT_EQ(0U, ignored_patterns.size()); |
| |
| base::Value::List args; |
| args.Append(GetOriginList(1)); |
| handler()->HandleIgnoreOriginsForNotificationPermissionReview(args); |
| |
| // Check there is 1 origin in ignore list. |
| content_settings->GetSettingsForOneType( |
| ContentSettingsType::NOTIFICATION_PERMISSION_REVIEW, &ignored_patterns); |
| ASSERT_EQ(1U, ignored_patterns.size()); |
| |
| ValidateNotificationPermissionUpdate(); |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, HandleBlockNotificationPermissionForOrigins) { |
| base::test::ScopedFeatureList scoped_feature; |
| scoped_feature.InitAndEnableFeature( |
| ::features::kSafetyCheckNotificationPermissions); |
| |
| base::Value::List args; |
| base::Value::List origins = GetOriginList(2); |
| args.Append(origins.Clone()); |
| |
| handler()->HandleBlockNotificationPermissionForOrigins(args); |
| |
| // Check the permission for the two origins is block. |
| HostContentSettingsMap* content_settings = |
| HostContentSettingsMapFactory::GetForProfile(profile()); |
| ContentSettingsForOneType notification_permissions; |
| content_settings->GetSettingsForOneType(ContentSettingsType::NOTIFICATIONS, |
| ¬ification_permissions); |
| auto type = content_settings->GetContentSetting( |
| GURL(origins[0].GetString()), GURL(), ContentSettingsType::NOTIFICATIONS); |
| ASSERT_EQ(CONTENT_SETTING_BLOCK, type); |
| |
| type = content_settings->GetContentSetting( |
| GURL(origins[1].GetString()), GURL(), ContentSettingsType::NOTIFICATIONS); |
| ASSERT_EQ(CONTENT_SETTING_BLOCK, type); |
| |
| ValidateNotificationPermissionUpdate(); |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, HandleAllowNotificationPermissionForOrigins) { |
| base::test::ScopedFeatureList scoped_feature; |
| scoped_feature.InitAndEnableFeature( |
| ::features::kSafetyCheckNotificationPermissions); |
| |
| base::Value::List args; |
| base::Value::List origins = GetOriginList(2); |
| args.Append(origins.Clone()); |
| handler()->HandleAllowNotificationPermissionForOrigins(args); |
| |
| // Check the permission for the two origins is allow. |
| HostContentSettingsMap* content_settings = |
| HostContentSettingsMapFactory::GetForProfile(profile()); |
| ContentSettingsForOneType notification_permissions; |
| content_settings->GetSettingsForOneType(ContentSettingsType::NOTIFICATIONS, |
| ¬ification_permissions); |
| auto type = content_settings->GetContentSetting( |
| GURL(origins[0].GetString()), GURL(), ContentSettingsType::NOTIFICATIONS); |
| ASSERT_EQ(CONTENT_SETTING_ALLOW, type); |
| |
| type = content_settings->GetContentSetting( |
| GURL(origins[1].GetString()), GURL(), ContentSettingsType::NOTIFICATIONS); |
| ASSERT_EQ(CONTENT_SETTING_ALLOW, type); |
| |
| ValidateNotificationPermissionUpdate(); |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, HandleResetNotificationPermissionForOrigins) { |
| base::test::ScopedFeatureList scoped_feature; |
| scoped_feature.InitAndEnableFeature( |
| ::features::kSafetyCheckNotificationPermissions); |
| |
| HostContentSettingsMap* content_settings = |
| HostContentSettingsMapFactory::GetForProfile(profile()); |
| base::Value::List args; |
| base::Value::List origins = GetOriginList(1); |
| args.Append(origins.Clone()); |
| |
| content_settings->SetContentSettingCustomScope( |
| ContentSettingsPattern::FromString(origins[0].GetString()), |
| ContentSettingsPattern::Wildcard(), ContentSettingsType::NOTIFICATIONS, |
| CONTENT_SETTING_ALLOW); |
| |
| handler()->HandleResetNotificationPermissionForOrigins(args); |
| |
| // Check the permission for the origin is reset. |
| auto type = content_settings->GetContentSetting( |
| GURL(origins[0].GetString()), GURL(), ContentSettingsType::NOTIFICATIONS); |
| ASSERT_EQ(CONTENT_SETTING_ASK, type); |
| |
| ValidateNotificationPermissionUpdate(); |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, PopulateNotificationPermissionReviewData) { |
| base::test::ScopedFeatureList scoped_feature; |
| scoped_feature.InitAndEnableFeature( |
| ::features::kSafetyCheckNotificationPermissions); |
| |
| // Add a couple of notification permission and check they appear in review |
| // list. |
| HostContentSettingsMap* map = |
| HostContentSettingsMapFactory::GetForProfile(profile()); |
| GURL urls[] = {GURL("https://google.com:443"), |
| GURL("https://www.youtube.com:443"), |
| GURL("https://www.example.com:443")}; |
| |
| map->SetContentSettingDefaultScope(urls[0], GURL(), |
| ContentSettingsType::NOTIFICATIONS, |
| CONTENT_SETTING_ALLOW); |
| map->SetContentSettingDefaultScope(urls[1], GURL(), |
| ContentSettingsType::NOTIFICATIONS, |
| CONTENT_SETTING_ALLOW); |
| map->SetContentSettingDefaultScope(urls[2], GURL(), |
| ContentSettingsType::NOTIFICATIONS, |
| CONTENT_SETTING_ALLOW); |
| |
| // Record initial display date to enable comparing dictionaries for |
| // NotificationEngagementService. |
| auto* notification_engagement_service = |
| NotificationsEngagementServiceFactory::GetForProfile(profile()); |
| std::string displayedDate = |
| notification_engagement_service->GetBucketLabel(base::Time::Now()); |
| |
| auto* site_engagement_service = |
| site_engagement::SiteEngagementServiceFactory::GetForProfile(profile()); |
| |
| // Set a host to have minimum engagement. This should be in review list. |
| RecordNotification(notification_engagement_service, urls[0], 1); |
| site_engagement::SiteEngagementScore score = |
| site_engagement_service->CreateEngagementScore(urls[0]); |
| score.Reset(0.5, GetReferenceTime()); |
| score.Commit(); |
| EXPECT_EQ(blink::mojom::EngagementLevel::MINIMAL, |
| site_engagement_service->GetEngagementLevel(urls[0])); |
| |
| // Set a host to have large number of notifications, but low engagement. This |
| // should be in review list. |
| RecordNotification(notification_engagement_service, urls[1], 5); |
| site_engagement_service->AddPointsForTesting(urls[1], 1.0); |
| EXPECT_EQ(blink::mojom::EngagementLevel::LOW, |
| site_engagement_service->GetEngagementLevel(urls[1])); |
| |
| // Set a host to have medium engagement and high notification count. This |
| // should not be in review list. |
| RecordNotification(notification_engagement_service, urls[2], 5); |
| site_engagement_service->AddPointsForTesting(urls[2], 50.0); |
| EXPECT_EQ(blink::mojom::EngagementLevel::MEDIUM, |
| site_engagement_service->GetEngagementLevel(urls[2])); |
| |
| const auto& notification_permissions = |
| handler()->PopulateNotificationPermissionReviewData(); |
| // Check if resulting list contains only the expected URLs. They should be in |
| // descending order of notification count. |
| EXPECT_EQ(2UL, notification_permissions.size()); |
| EXPECT_EQ("https://www.youtube.com:443", |
| *notification_permissions[0].GetDict().FindString( |
| site_settings::kOrigin)); |
| EXPECT_EQ("https://google.com:443", |
| *notification_permissions[1].GetDict().FindString( |
| site_settings::kOrigin)); |
| |
| // Increasing notification count also promotes host in the list. |
| RecordNotification(notification_engagement_service, |
| GURL("https://google.com:443"), 10); |
| const auto& updated_notification_permissions = |
| handler()->PopulateNotificationPermissionReviewData(); |
| EXPECT_EQ(2UL, updated_notification_permissions.size()); |
| EXPECT_EQ("https://google.com:443", |
| *updated_notification_permissions[0].GetDict().FindString( |
| site_settings::kOrigin)); |
| EXPECT_EQ("https://www.youtube.com:443", |
| *updated_notification_permissions[1].GetDict().FindString( |
| site_settings::kOrigin)); |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, |
| HandleUndoIgnoreOriginsForNotificationPermissionReview) { |
| base::Value::List args; |
| args.Append(GetOriginList(1)); |
| handler()->HandleIgnoreOriginsForNotificationPermissionReview(args); |
| |
| // Check there is 1 origin in ignore list. |
| HostContentSettingsMap* content_settings = |
| HostContentSettingsMapFactory::GetForProfile(profile()); |
| ContentSettingsForOneType ignored_patterns; |
| ASSERT_EQ(0U, ignored_patterns.size()); |
| content_settings->GetSettingsForOneType( |
| ContentSettingsType::NOTIFICATION_PERMISSION_REVIEW, &ignored_patterns); |
| ASSERT_EQ(1U, ignored_patterns.size()); |
| |
| // Check there are no origins in ignore list. |
| handler()->HandleUndoIgnoreOriginsForNotificationPermissionReview(args); |
| content_settings->GetSettingsForOneType( |
| ContentSettingsType::NOTIFICATION_PERMISSION_REVIEW, &ignored_patterns); |
| ASSERT_EQ(0U, ignored_patterns.size()); |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, |
| SendNotificationPermissionReviewList_FeatureEnabled) { |
| base::test::ScopedFeatureList scoped_feature; |
| scoped_feature.InitAndEnableFeature( |
| ::features::kSafetyCheckNotificationPermissions); |
| |
| handler()->SendNotificationPermissionReviewList(); |
| |
| ValidateNotificationPermissionUpdate(); |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, |
| SendNotificationPermissionReviewList_FeatureDisabled) { |
| base::test::ScopedFeatureList scoped_feature; |
| scoped_feature.InitAndDisableFeature( |
| ::features::kSafetyCheckNotificationPermissions); |
| |
| handler()->SendNotificationPermissionReviewList(); |
| |
| ASSERT_EQ(0U, web_ui()->call_data().size()); |
| } |
| |
| TEST_F(SiteSettingsHandlerTest, IsolatedWebAppUsageInfo) { |
| std::string iwa_url = |
| "isolated-app://aerugqztij5biqquuk3mfwpsaibuegaqcitgfchwuosuofdjabzqaaic"; |
| SetupModelsWithIsolatedWebAppData(iwa_url, 1000); |
| |
| base::Value::List args; |
| args.Append(iwa_url); |
| handler()->HandleFetchUsageTotal(args); |
| handler()->ServicePendingRequests(); |
| |
| ValidateUsageInfo( |
| /*expected_usage_host=*/iwa_url, /*expected_usage_string=*/"1,000 B", |
| /*expected_cookie_string=*/"", |
| /*expected_fps_member_count_string=*/"", /*expected_fps_policy=*/false); |
| } |
| |
| } // namespace settings |