| // 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 <set> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/barrier_closure.h" |
| #include "base/containers/contains.h" |
| #include "base/containers/flat_set.h" |
| #include "base/feature_list.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/functional/overloaded.h" |
| #include "base/i18n/message_formatter.h" |
| #include "base/i18n/number_formatting.h" |
| #include "base/json/values_util.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/metrics/user_metrics.h" |
| #include "base/no_destructor.h" |
| #include "base/ranges/algorithm.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/values.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/access_context_audit_service_factory.h" |
| #include "chrome/browser/browsing_data/chrome_browsing_data_model_delegate.h" |
| #include "chrome/browser/browsing_data/cookies_tree_model.h" |
| #include "chrome/browser/browsing_topics/browsing_topics_service_factory.h" |
| #include "chrome/browser/content_settings/chrome_content_settings_utils.h" |
| #include "chrome/browser/content_settings/host_content_settings_map_factory.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/hid/hid_chooser_context.h" |
| #include "chrome/browser/hid/hid_chooser_context_factory.h" |
| #include "chrome/browser/media/unified_autoplay_config.h" |
| #include "chrome/browser/permissions/notification_permission_review_service_factory.h" |
| #include "chrome/browser/permissions/permission_decision_auto_blocker_factory.h" |
| #include "chrome/browser/privacy_sandbox/privacy_sandbox_service.h" |
| #include "chrome/browser/privacy_sandbox/privacy_sandbox_service_factory.h" |
| #include "chrome/browser/serial/serial_chooser_context.h" |
| #include "chrome/browser/serial/serial_chooser_context_factory.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_list.h" |
| #include "chrome/browser/ui/page_info/page_info_infobar_delegate.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/browser/ui/url_identity.h" |
| #include "chrome/browser/ui/webui/settings/recent_site_settings_helper.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/common/chrome_features.h" |
| #include "chrome/common/extensions/manifest_handlers/app_launch_info.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "components/browsing_data/content/browsing_data_model.h" |
| #include "components/browsing_topics/browsing_topics_service.h" |
| #include "components/content_settings/core/browser/cookie_settings.h" |
| #include "components/content_settings/core/browser/website_settings_registry.h" |
| #include "components/content_settings/core/common/content_settings.h" |
| #include "components/content_settings/core/common/content_settings_types.h" |
| #include "components/content_settings/core/common/content_settings_utils.h" |
| #include "components/content_settings/core/common/pref_names.h" |
| #include "components/crx_file/id_util.h" |
| #include "components/infobars/content/content_infobar_manager.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/prefs/pref_change_registrar.h" |
| #include "components/prefs/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_service.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/browsing_data_filter_builder.h" |
| #include "content/public/browser/browsing_data_remover.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/browser/storage_partition_config.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_ui.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/common/url_constants.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/common/permissions/api_permission.h" |
| #include "extensions/common/permissions/permissions_data.h" |
| #include "services/network/public/cpp/is_potentially_trustworthy.h" |
| #include "storage/common/file_system/file_system_util.h" |
| #include "third_party/blink/public/common/page/page_zoom.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/text/bytes_formatting.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| #include "url/url_constants.h" |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| #include "components/user_manager/user_manager.h" |
| #endif |
| |
| #if BUILDFLAG(IS_WIN) |
| #include "chrome/browser/media/cdm_document_service_impl.h" |
| #endif |
| |
| #if BUILDFLAG(ENABLE_EXTENSIONS) |
| #include "extensions/common/constants.h" |
| #endif |
| |
| using extensions::mojom::APIPermissionID; |
| |
| namespace settings { |
| |
| namespace { |
| |
| using GroupingKey = SiteSettingsHandler::GroupingKey; |
| |
| // Keys of the dictionary returned by HandleIsPatternValidForType. |
| constexpr char kIsValidKey[] = "isValid"; |
| constexpr char kReasonKey[] = "reason"; |
| |
| constexpr char kEffectiveTopLevelDomainPlus1Name[] = "etldPlus1"; |
| constexpr char kOriginList[] = "origins"; |
| constexpr char kNumCookies[] = "numCookies"; |
| constexpr char kHasPermissionSettings[] = "hasPermissionSettings"; |
| constexpr char kHasInstalledPWA[] = "hasInstalledPWA"; |
| constexpr char kIsInstalled[] = "isInstalled"; |
| constexpr char kFpsOwner[] = "fpsOwner"; |
| constexpr char kFpsNumMembers[] = "fpsNumMembers"; |
| constexpr char kFpsEnterpriseManaged[] = "fpsEnterpriseManaged"; |
| constexpr char kZoom[] = "zoom"; |
| |
| constexpr uint16_t kHttpsDefaultPort = 443; |
| |
| // Content types for chooser data. |
| constexpr ContentSettingsType kChooserDataContentSettingsTypes[] = { |
| ContentSettingsType::BLUETOOTH_CHOOSER_DATA, |
| ContentSettingsType::HID_CHOOSER_DATA, |
| ContentSettingsType::SERIAL_CHOOSER_DATA, |
| ContentSettingsType::USB_CHOOSER_DATA, |
| }; |
| |
| // These values are persisted to logs. Entries should not be renumbered and |
| // numeric values should never be reused. |
| enum class AllSitesAction2 { |
| kLoadPage = 0, |
| kResetSiteGroupPermissions = 1, |
| kResetOriginPermissions = 2, |
| kClearAllData = 3, |
| kClearSiteGroupData = 4, |
| kClearOriginData = 5, |
| kEnterSiteDetails = 6, |
| kRemoveSiteGroup = 7, |
| kRemoveOrigin = 8, |
| kRemoveOriginPartitioned = 9, |
| kFilterByFpsOwner = 10, |
| kDeleteForEntireFps = 11, |
| kMaxValue = kDeleteForEntireFps, |
| }; |
| |
| // Return an appropriate API Permission ID for the given string name. |
| APIPermissionID APIPermissionFromGroupName(std::string type) { |
| // Once there are more than two groups to consider, this should be changed to |
| // something better than if's. |
| |
| if (site_settings::ContentSettingsTypeFromGroupName(type) == |
| ContentSettingsType::GEOLOCATION) { |
| return APIPermissionID::kGeolocation; |
| } |
| |
| if (site_settings::ContentSettingsTypeFromGroupName(type) == |
| ContentSettingsType::NOTIFICATIONS) { |
| return APIPermissionID::kNotifications; |
| } |
| |
| return APIPermissionID::kInvalid; |
| } |
| |
| // Asks the |profile| for hosted apps which have the |permission| set, and |
| // adds their web extent and launch URL to the |exceptions| list. |
| void AddExceptionsGrantedByHostedApps(content::BrowserContext* context, |
| APIPermissionID permission, |
| base::Value::List* exceptions) { |
| const extensions::ExtensionSet& extensions = |
| extensions::ExtensionRegistry::Get(context)->enabled_extensions(); |
| for (extensions::ExtensionSet::const_iterator extension = extensions.begin(); |
| extension != extensions.end(); ++extension) { |
| if (!(*extension)->is_hosted_app() || |
| !(*extension)->permissions_data()->HasAPIPermission(permission)) { |
| continue; |
| } |
| |
| const extensions::URLPatternSet& web_extent = (*extension)->web_extent(); |
| // Add patterns from web extent. |
| for (const auto& pattern : web_extent) { |
| std::string url_pattern = pattern.GetAsString(); |
| site_settings::AddExceptionForHostedApp(url_pattern, *extension->get(), |
| exceptions); |
| } |
| // Retrieve the launch URL. |
| GURL launch_url = |
| extensions::AppLaunchInfo::GetLaunchWebURL(extension->get()); |
| // Skip adding the launch URL if it is part of the web extent. |
| if (web_extent.MatchesURL(launch_url)) |
| continue; |
| site_settings::AddExceptionForHostedApp(launch_url.spec(), |
| *extension->get(), exceptions); |
| } |
| } |
| |
| base::flat_set<url::Origin> GetInstalledAppOrigins(Profile* profile) { |
| if (!apps::AppServiceProxyFactory::IsAppServiceAvailableForProfile(profile)) |
| return base::flat_set<url::Origin>(); |
| |
| std::vector<url::Origin> origins; |
| apps::AppServiceProxyFactory::GetForProfile(profile) |
| ->AppRegistryCache() |
| .ForEachApp([&origins](const apps::AppUpdate& update) { |
| if (update.AppType() == apps::AppType::kWeb || |
| update.AppType() == apps::AppType::kSystemWeb) { |
| // For web apps, |PublisherId()| is set to the start URL. |
| const GURL start_url(update.PublisherId()); |
| DCHECK(start_url.is_valid()); |
| origins.push_back(url::Origin::Create(start_url)); |
| } |
| }); |
| return base::flat_set<url::Origin>(std::move(origins)); |
| } |
| |
| // Placeholder opaque origin until a valid origin is added. If a group only has |
| // the placeholder origin, then replace placeholder with the GroupingKey. |
| const url::Origin& GetPlaceholderOrigin() { |
| static auto placeholder_origin = base::NoDestructor(url::Origin()); |
| return *placeholder_origin; |
| } |
| |
| // Inserts |origin| into |site_group_map|, creating a new group if necessary. |
| // Origins are grouped by their GroupingKey. If |origin| is HTTP/HTTPS, the |
| // GroupingKey will be |partition_etld_plus1| or |origin|'s eTLD+1. The |
| // GroupingKey for other schemes will be |origin|. |
| // There are three cases: |
| // 1. The GroupingKey is not yet in |site_group_map|. We add the GroupingKey |
| // to |site_group_map|. If |origin| is an eTLD+1 cookie origin, |
| // put a placeholder origin in the new group. |
| // 2. The GroupingKey is in |site_group_map| and |origin| is an eTLD+1 cookie |
| // origin. This means case 1 has already happened and nothing more needs to |
| // be done. |
| // 3. The GroupingKey is in |site_group_map| and |origin| is NOT an eTLD+1 |
| // cookie origin. For a cookie origin, if a https origin with same host and |
| // partitioned status exists, nothing more needs to be done. |
| // In case 3, we try to add |origin| to the set of origins for the GroupingKey. |
| // If an existing origin is a placeholder, delete it, because the placeholder |
| // is no longer needed. |
| void InsertOriginIntoGroup( |
| SiteSettingsHandler::AllSitesMap* site_group_map, |
| const url::Origin& origin, |
| bool is_origin_with_cookies = false, |
| absl::optional<GroupingKey> partition_grouping_key = absl::nullopt) { |
| const url::Origin& placeholder_origin = GetPlaceholderOrigin(); |
| bool is_partitioned = partition_grouping_key.has_value(); |
| GroupingKey grouping_key = partition_grouping_key.has_value() |
| ? partition_grouping_key.value() |
| : GroupingKey::Create(origin); |
| auto group = site_group_map->find(grouping_key); |
| bool is_etld_plus1_cookie_origin = |
| is_origin_with_cookies && origin.GetURL().SchemeIsHTTPOrHTTPS() && |
| grouping_key.GetEtldPlusOne() == origin.host(); |
| |
| // Case 1: |
| if (group == site_group_map->end()) { |
| url::Origin new_origin = |
| is_etld_plus1_cookie_origin ? placeholder_origin : origin; |
| site_group_map->emplace( |
| grouping_key, |
| std::set<std::pair<url::Origin, bool>>({{new_origin, is_partitioned}})); |
| return; |
| } |
| // Case 2: |
| if (is_etld_plus1_cookie_origin && !is_partitioned) { |
| return; |
| } |
| // Case 3: |
| if (is_origin_with_cookies && origin.scheme() == url::kHttpScheme) { |
| // Cookies ignore schemes, so try and see if a https schemed version |
| // already exists in the origin list, if not, then add the http schemed |
| // version into the map. |
| auto https_origin = url::Origin::CreateFromNormalizedTuple( |
| url::kHttpsScheme, origin.host(), kHttpsDefaultPort); |
| if (group->second.find({https_origin, is_partitioned}) != |
| group->second.end()) { |
| return; |
| } |
| } |
| group->second.insert({origin, is_partitioned}); |
| auto placeholder = group->second.find({placeholder_origin, is_partitioned}); |
| if (placeholder != group->second.end()) { |
| group->second.erase(placeholder); |
| } |
| } |
| |
| // Update the storage data in |origin_size_map|. |
| void UpdateDataForOrigin(const url::Origin& origin, |
| const int64_t size, |
| std::map<url::Origin, int64_t>* origin_size_map) { |
| if (size > 0) { |
| (*origin_size_map)[origin] += size; |
| } |
| } |
| |
| // Converts |etld_plus1| into an origin representation by adding HTTP(S) scheme. |
| url::Origin ConvertEtldToOrigin(const std::string etld_plus1, bool secure) { |
| if (secure) { |
| return url::Origin::CreateFromNormalizedTuple(url::kHttpsScheme, etld_plus1, |
| kHttpsDefaultPort); |
| } |
| return url::Origin::CreateFromNormalizedTuple(url::kHttpScheme, etld_plus1, |
| 80); |
| } |
| |
| bool IsPatternValidForType(const std::string& pattern_string, |
| ContentSettingsType content_type, |
| Profile* profile, |
| std::string* out_error) { |
| ContentSettingsPattern pattern = |
| ContentSettingsPattern::FromString(pattern_string); |
| |
| HostContentSettingsMap* map = |
| HostContentSettingsMapFactory::GetForProfile(profile); |
| |
| // Don't allow patterns for WebUI schemes, even though it's a valid pattern. |
| // WebUI permissions are controlled by ContentSettingsRegistry |
| // AllowlistedSchemes and WebUIAllowlist. Users shouldn't be able to grant |
| // extra permissions or revoke existing permissions. |
| if (pattern.GetScheme() == ContentSettingsPattern::SCHEME_CHROME || |
| pattern.GetScheme() == ContentSettingsPattern::SCHEME_CHROMEUNTRUSTED || |
| pattern.GetScheme() == ContentSettingsPattern::SCHEME_DEVTOOLS || |
| pattern.GetScheme() == ContentSettingsPattern::SCHEME_CHROMESEARCH) { |
| *out_error = l10n_util::GetStringUTF8(IDS_SETTINGS_NOT_VALID_WEB_ADDRESS); |
| return false; |
| } |
| |
| // Don't allow an input of '*', even though it's a valid pattern. This |
| // changes the default setting. |
| if (!pattern.IsValid() || pattern == ContentSettingsPattern::Wildcard()) { |
| *out_error = l10n_util::GetStringUTF8(IDS_SETTINGS_NOT_VALID_WEB_ADDRESS); |
| return false; |
| } |
| |
| // Check if a setting can be set for this url and setting type, and if not, |
| // return false with a string saying why. |
| GURL url(pattern_string); |
| if (url.is_valid() && map->IsRestrictedToSecureOrigins(content_type) && |
| !network::IsUrlPotentiallyTrustworthy(url)) { |
| *out_error = l10n_util::GetStringUTF8( |
| IDS_SETTINGS_NOT_VALID_WEB_ADDRESS_FOR_CONTENT_TYPE); |
| return false; |
| } |
| |
| // The pattern is valid. |
| return true; |
| } |
| |
| void UpdateDataFromModel(SiteSettingsHandler::AllSitesMap* all_sites_map, |
| std::map<url::Origin, int64_t>* origin_size_map, |
| const url::Origin& origin, |
| int64_t size) { |
| UpdateDataForOrigin(origin, size, origin_size_map); |
| InsertOriginIntoGroup(all_sites_map, origin); |
| } |
| |
| void LogAllSitesAction(AllSitesAction2 action) { |
| UMA_HISTOGRAM_ENUMERATION("WebsiteSettings.AllSitesAction2", action); |
| } |
| |
| int GetNumCookieExceptionsOfTypes(HostContentSettingsMap* map, |
| const std::set<ContentSetting> types) { |
| ContentSettingsForOneType output; |
| map->GetSettingsForOneType(ContentSettingsType::COOKIES, &output); |
| return base::ranges::count_if( |
| output, [types](const ContentSettingPatternSource setting) { |
| return types.count( |
| content_settings::ValueToContentSetting(setting.setting_value)); |
| }); |
| } |
| |
| std::string GetCookieSettingDescription(Profile* profile) { |
| HostContentSettingsMap* map = |
| HostContentSettingsMapFactory::GetForProfile(profile); |
| auto content_setting = |
| map->GetDefaultContentSetting(ContentSettingsType::COOKIES, nullptr); |
| |
| auto control_mode = static_cast<content_settings::CookieControlsMode>( |
| profile->GetPrefs()->GetInteger(prefs::kCookieControlsMode)); |
| |
| // Determine what the effective cookie setting is. These conditions are not |
| // mutually exclusive and rely on ordering. |
| if (content_setting == ContentSetting::CONTENT_SETTING_BLOCK) { |
| return l10n_util::GetPluralStringFUTF8( |
| IDS_SETTINGS_SITE_SETTINGS_COOKIES_BLOCK, |
| GetNumCookieExceptionsOfTypes( |
| map, {ContentSetting::CONTENT_SETTING_ALLOW, |
| ContentSetting::CONTENT_SETTING_SESSION_ONLY})); |
| } |
| switch (control_mode) { |
| case content_settings::CookieControlsMode::kBlockThirdParty: |
| return l10n_util::GetStringUTF8( |
| IDS_SETTINGS_SITE_SETTINGS_COOKIES_BLOCK_THIRD_PARTY); |
| case content_settings::CookieControlsMode::kIncognitoOnly: |
| return l10n_util::GetStringUTF8( |
| IDS_SETTINGS_SITE_SETTINGS_COOKIES_BLOCK_THIRD_PARTY_INCOGNITO); |
| case content_settings::CookieControlsMode::kOff: |
| // We do not make a distinction between allow and clear on exit. |
| return l10n_util::GetPluralStringFUTF8( |
| IDS_SETTINGS_SITE_SETTINGS_COOKIES_ALLOW, |
| GetNumCookieExceptionsOfTypes( |
| map, {ContentSetting::CONTENT_SETTING_BLOCK})); |
| } |
| NOTREACHED(); |
| } |
| |
| // Removes all nodes from |model| which match |origin| and |etld_plus1|. At |
| // least one of |origin| or |etld_plus1| must be set. If only |origin| is set, |
| // then unpartitioned storage for that origin is removed. If only |etld_plus1| |
| // is set, then any unpartitioned storage which matches that etld + 1, or |
| // partitioned storage where it is the partitioning site, is removed. If both |
| // |origin| and |etld_plus1| is set, then only storage for |origin| partitioned |
| // by |etld_plus1| is removed. |
| void RemoveMatchingNodes(CookiesTreeModel* model, |
| absl::optional<std::string> origin, |
| absl::optional<std::string> etld_plus1) { |
| DCHECK(origin || etld_plus1); |
| std::vector<CookieTreeNode*> nodes_to_delete; |
| |
| for (const auto& host_node : model->GetRoot()->children()) { |
| bool origin_matches = |
| origin && |
| *origin == host_node->GetDetailedInfo().origin.GetURL().spec(); |
| |
| if (origin && !origin_matches) { |
| // If the origin is set, host nodes which do not match that origin cannot |
| // contain storage targeted for removal. |
| continue; |
| } |
| |
| std::string host_node_etld_plus1 = |
| net::registry_controlled_domains::GetDomainAndRegistry( |
| base::UTF16ToUTF8(host_node->GetTitle()), |
| net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES); |
| |
| bool etld_plus1_matches = etld_plus1 && *etld_plus1 == host_node_etld_plus1; |
| |
| for (const auto& storage_type_node : host_node->children()) { |
| if (storage_type_node->GetDetailedInfo().node_type != |
| CookieTreeNode::DetailedInfo::TYPE_COOKIES) { |
| // Non cookie storage cannot (currently) be partitioned. |
| if (origin && etld_plus1) { |
| continue; |
| } |
| |
| if (origin_matches || etld_plus1_matches) { |
| nodes_to_delete.push_back(storage_type_node.get()); |
| continue; |
| } |
| } else { |
| // Every cookie must be inspected to confirm partition state. |
| // TODO(crbug.com/1271155): This is slow, and should be addressed when |
| // the CookiesTreeModel is deprecated. |
| for (const auto& cookie_node : storage_type_node->children()) { |
| const auto& cookie = cookie_node->GetDetailedInfo().cookie; |
| if (!cookie->IsPartitioned() && |
| (origin_matches || etld_plus1_matches) && |
| (!origin || !etld_plus1)) { |
| nodes_to_delete.push_back(cookie_node.get()); |
| continue; |
| } |
| if (cookie->IsPartitioned()) { |
| const auto& partition_site = |
| cookie->PartitionKey()->site().GetURL().host(); |
| |
| // If an origin has been set, it must match the origin of the |
| // current node, which means it can be ignored. |
| DCHECK(!origin || origin_matches); |
| |
| if (etld_plus1 && partition_site == *etld_plus1) { |
| nodes_to_delete.push_back(cookie_node.get()); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| for (auto* node : nodes_to_delete) |
| model->DeleteCookieNode(node); |
| } |
| |
| // Returns the registable domain (eTLD+1) for the `origin`. If it doesn't exist, |
| // returns the host. |
| std::string GetEtldPlusOne(const url::Origin& origin) { |
| auto eltd_plus_one = net::registry_controlled_domains::GetDomainAndRegistry( |
| origin, net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES); |
| return eltd_plus_one.empty() ? origin.host() : eltd_plus_one; |
| } |
| |
| // 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 + |
| "/")); |
| } |
| |
| // Iterates over host nodes in `tree_model` which contains all sites that have |
| // storage set and uses them to retrieve first party set membership information. |
| // Returns a map of site eTLD+1 matched with their FPS owner and count of first |
| // party set members. |
| std::map<std::string, std::pair<std::string, int>> GetFpsMap( |
| PrivacySandboxService* privacy_sandbox_service, |
| CookiesTreeModel* tree_model) { |
| // Used to count unique eTLD+1 owned by a FPS owner. |
| std::map<std::string, std::set<std::string>> fps_owner_to_members; |
| |
| // Count members by unique eTLD+1 for each first party set. |
| for (const auto& host_node : tree_model->GetRoot()->children()) { |
| std::string etld_plus1 = |
| GetEtldPlusOne(host_node->GetDetailedInfo().origin); |
| auto schemeful_site = ConvertEtldToSchemefulSite(etld_plus1); |
| auto fps_owner = |
| privacy_sandbox_service->GetFirstPartySetOwner(schemeful_site.GetURL()); |
| if (fps_owner.has_value()) { |
| fps_owner_to_members[fps_owner->GetURL().host()].insert(etld_plus1); |
| } |
| } |
| |
| // site eTLD+1 : {owner site eTLD+1, # of sites in that first party set} |
| std::map<std::string, std::pair<std::string, int>> fps_map; |
| for (auto fps : fps_owner_to_members) { |
| // Set fps owner and count of members for each eTLD+1 |
| for (auto member : fps.second) { |
| fps_map[member] = {fps.first, fps.second.size()}; |
| } |
| } |
| |
| return fps_map; |
| } |
| |
| // Resolves |origin| to the correct value for its site group if it is a |
| // placeholder origin. |
| url::Origin ResolveOriginInSiteGroup(const GroupingKey& grouping_key, |
| const url::Origin& origin) { |
| if (origin != GetPlaceholderOrigin()) { |
| return origin; |
| } |
| if (auto etld_plus1 = grouping_key.GetEtldPlusOne(); etld_plus1.has_value()) { |
| return ConvertEtldToOrigin(*etld_plus1, /*secure=*/false); |
| } |
| return *grouping_key.GetOrigin(); |
| } |
| |
| // Converts a given |site_group_map| to a list of base::Value::Dicts, adding |
| // the site engagement score for each origin. |
| void ConvertSiteGroupMapToList( |
| const SiteSettingsHandler::AllSitesMap& site_group_map, |
| const std::set<url::Origin>& origin_permission_set, |
| base::Value::List* list_value, |
| Profile* profile, |
| CookiesTreeModel* tree_model) { |
| DCHECK(profile); |
| auto* privacy_sandbox_service = |
| PrivacySandboxServiceFactory::GetForProfile(profile); |
| auto fps_map = GetFpsMap(privacy_sandbox_service, tree_model); |
| base::flat_set<url::Origin> installed_origins = |
| GetInstalledAppOrigins(profile); |
| site_engagement::SiteEngagementService* engagement_service = |
| site_engagement::SiteEngagementService::Get(profile); |
| for (const auto& entry : site_group_map) { |
| base::Value::Dict site_group; |
| const GroupingKey& grouping_key = entry.first; |
| site_group.Set(kEffectiveTopLevelDomainPlus1Name, grouping_key.Serialize()); |
| |
| // eTLD+1 is the effective top level domain + 1. |
| absl::optional<std::string> etld_plus1 = grouping_key.GetEtldPlusOne(); |
| absl::optional<url::Origin> group_origin = grouping_key.GetOrigin(); |
| CHECK(etld_plus1 || group_origin); |
| site_group.Set(site_settings::kDisplayName, |
| etld_plus1.has_value() |
| ? *etld_plus1 |
| : site_settings::GetDisplayNameForGURL( |
| profile, group_origin->GetURL(), |
| /*hostname_only=*/false)); |
| |
| bool has_installed_pwa = false; |
| base::Value::List origin_list; |
| for (const auto& origin_is_partitioned : entry.second) { |
| const url::Origin& origin = origin_is_partitioned.first; |
| bool is_partitioned = origin_is_partitioned.second; |
| base::Value::Dict origin_object; |
| // If origin is placeholder, use the grouping key for the origin. |
| origin_object.Set( |
| "origin", |
| ResolveOriginInSiteGroup(grouping_key, origin).GetURL().spec()); |
| origin_object.Set("isPartitioned", is_partitioned); |
| origin_object.Set("engagement", |
| engagement_service->GetScore(origin.GetURL())); |
| origin_object.Set("usage", 0); |
| origin_object.Set(kNumCookies, 0); |
| |
| bool is_installed = installed_origins.contains(origin); |
| if (is_installed) |
| has_installed_pwa = true; |
| origin_object.Set(kIsInstalled, is_installed); |
| |
| origin_object.Set(kHasPermissionSettings, |
| base::Contains(origin_permission_set, origin)); |
| origin_list.Append(std::move(origin_object)); |
| } |
| site_group.Set(kHasInstalledPWA, has_installed_pwa); |
| site_group.Set(kNumCookies, 0); |
| site_group.Set(kOriginList, std::move(origin_list)); |
| if (etld_plus1.has_value() && fps_map.count(*etld_plus1)) { |
| site_group.Set(kFpsOwner, fps_map[*etld_plus1].first); |
| site_group.Set(kFpsNumMembers, fps_map[*etld_plus1].second); |
| auto schemeful_site = ConvertEtldToSchemefulSite(*etld_plus1); |
| site_group.Set(kFpsEnterpriseManaged, |
| privacy_sandbox_service->IsPartOfManagedFirstPartySet( |
| schemeful_site)); |
| } |
| list_value->Append(std::move(site_group)); |
| } |
| } |
| |
| bool ShouldAddToNotificationPermissionReviewList( |
| site_engagement::SiteEngagementService* service, |
| GURL url, |
| int notification_count) { |
| // The notification permission should be added to the list if one of the |
| // criteria below holds: |
| // - Site engagement level is NONE OR MINIMAL and average daily notification |
| // count is more than 0. |
| // - Site engamment level is LOW and average daily notification count is |
| // more than 3. Otherwise, the notification permission should not be added |
| // to review list. |
| double score = service->GetScore(url); |
| int low_engagement_notification_limit = |
| features::kSafetyCheckNotificationPermissionsLowEnagementLimit.Get(); |
| bool is_low_engagement = |
| !site_engagement::SiteEngagementService::IsEngagementAtLeast( |
| score, blink::mojom::EngagementLevel::MEDIUM) && |
| notification_count > low_engagement_notification_limit; |
| int min_engagement_notification_limit = |
| features::kSafetyCheckNotificationPermissionsMinEnagementLimit.Get(); |
| bool is_minimal_engagement = |
| !site_engagement::SiteEngagementService::IsEngagementAtLeast( |
| score, blink::mojom::EngagementLevel::LOW) && |
| notification_count > min_engagement_notification_limit; |
| |
| return is_minimal_engagement || is_low_engagement; |
| } |
| |
| } // namespace |
| |
| // static |
| GroupingKey GroupingKey::Create(const url::Origin& origin) { |
| if (origin.GetURL().SchemeIsHTTPOrHTTPS()) { |
| return GroupingKey(settings::GetEtldPlusOne(origin)); |
| } |
| return GroupingKey(origin); |
| } |
| |
| // static |
| GroupingKey GroupingKey::CreateFromEtldPlus1(const std::string& etld_plus1) { |
| return GroupingKey(etld_plus1); |
| } |
| |
| GroupingKey::GroupingKey(const absl::variant<std::string, url::Origin>& value) |
| : value_(value) {} |
| |
| GroupingKey::GroupingKey(const GroupingKey& other) = default; |
| GroupingKey& GroupingKey::operator=(const GroupingKey& other) = default; |
| GroupingKey::~GroupingKey() = default; |
| |
| std::string GroupingKey::Serialize() const { |
| return absl::visit( |
| base::Overloaded{ |
| [](const std::string& etld_plus1) { return etld_plus1; }, |
| [](const url::Origin& origin) { return origin.GetURL().spec(); }}, |
| value_); |
| } |
| |
| absl::optional<std::string> GroupingKey::GetEtldPlusOne() const { |
| if (absl::holds_alternative<std::string>(value_)) { |
| return absl::get<std::string>(value_); |
| } |
| return absl::nullopt; |
| } |
| |
| absl::optional<url::Origin> GroupingKey::GetOrigin() const { |
| if (absl::holds_alternative<url::Origin>(value_)) { |
| return absl::get<url::Origin>(value_); |
| } |
| return absl::nullopt; |
| } |
| |
| url::Origin GroupingKey::ToOrigin() const { |
| return absl::visit( |
| base::Overloaded{[](const std::string& etld_plus1) { |
| return ConvertEtldToOrigin(etld_plus1, |
| /*secure=*/true); |
| }, |
| [](const url::Origin& origin) { return origin; }}, |
| value_); |
| } |
| |
| bool GroupingKey::operator<(const GroupingKey& other) const { |
| // To keep extensions and Isolated Web Apps grouped together, convert the |
| // GroupingKeys to an origin, putting all eTLD+1 keys under the same scheme |
| // (HTTPS), and sort based on this origin. |
| return ToOrigin() < other.ToOrigin(); |
| } |
| |
| SiteSettingsHandler::SiteSettingsHandler(Profile* profile) |
| : profile_(profile) {} |
| |
| SiteSettingsHandler::~SiteSettingsHandler() { |
| if (cookies_tree_model_) |
| cookies_tree_model_->RemoveCookiesTreeObserver(this); |
| } |
| |
| void SiteSettingsHandler::RegisterMessages() { |
| web_ui()->RegisterMessageCallback( |
| "fetchUsageTotal", |
| base::BindRepeating(&SiteSettingsHandler::HandleFetchUsageTotal, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "getFpsMembershipLabel", |
| base::BindRepeating(&SiteSettingsHandler::HandleGetFpsMembershipLabel, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "clearUnpartitionedUsage", |
| base::BindRepeating(&SiteSettingsHandler::HandleClearUnpartitionedUsage, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "clearPartitionedUsage", |
| base::BindRepeating(&SiteSettingsHandler::HandleClearPartitionedUsage, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "setDefaultValueForContentType", |
| base::BindRepeating( |
| &SiteSettingsHandler::HandleSetDefaultValueForContentType, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "getDefaultValueForContentType", |
| base::BindRepeating( |
| &SiteSettingsHandler::HandleGetDefaultValueForContentType, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "getAllSites", |
| base::BindRepeating(&SiteSettingsHandler::HandleGetAllSites, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "getCategoryList", |
| base::BindRepeating(&SiteSettingsHandler::HandleGetCategoryList, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "getCookieSettingDescription", |
| base::BindRepeating( |
| &SiteSettingsHandler::HandleGetCookieSettingDescription, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "getRecentSitePermissions", |
| base::BindRepeating(&SiteSettingsHandler::HandleGetRecentSitePermissions, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "getFormattedBytes", |
| base::BindRepeating(&SiteSettingsHandler::HandleGetFormattedBytes, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "getExceptionList", |
| base::BindRepeating(&SiteSettingsHandler::HandleGetExceptionList, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "getFileSystemGrants", |
| base::BindRepeating(&SiteSettingsHandler::HandleGetFileSystemGrants, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "revokeFileSystemGrant", |
| base::BindRepeating(&SiteSettingsHandler::HandleRevokeFileSystemGrant, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "revokeFileSystemGrants", |
| base::BindRepeating(&SiteSettingsHandler::HandleRevokeFileSystemGrants, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "getChooserExceptionList", |
| base::BindRepeating(&SiteSettingsHandler::HandleGetChooserExceptionList, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "getNotificationPermissionReview", |
| base::BindRepeating( |
| &SiteSettingsHandler::HandleGetNotificationPermissionReviewList, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "getOriginPermissions", |
| base::BindRepeating(&SiteSettingsHandler::HandleGetOriginPermissions, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "setOriginPermissions", |
| base::BindRepeating(&SiteSettingsHandler::HandleSetOriginPermissions, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "resetCategoryPermissionForPattern", |
| base::BindRepeating( |
| &SiteSettingsHandler::HandleResetCategoryPermissionForPattern, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "setCategoryPermissionForPattern", |
| base::BindRepeating( |
| &SiteSettingsHandler::HandleSetCategoryPermissionForPattern, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "resetChooserExceptionForSite", |
| base::BindRepeating( |
| &SiteSettingsHandler::HandleResetChooserExceptionForSite, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "ignoreNotificationPermissionReviewForOrigins", |
| base::BindRepeating( |
| &SiteSettingsHandler:: |
| HandleIgnoreOriginsForNotificationPermissionReview, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "resetNotificationPermissionForOrigins", |
| base::BindRepeating( |
| &SiteSettingsHandler::HandleResetNotificationPermissionForOrigins, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "blockNotificationPermissionForOrigins", |
| base::BindRepeating( |
| &SiteSettingsHandler::HandleBlockNotificationPermissionForOrigins, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "allowNotificationPermissionForOrigins", |
| base::BindRepeating( |
| &SiteSettingsHandler::HandleAllowNotificationPermissionForOrigins, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "undoIgnoreNotificationPermissionReviewForOrigins", |
| base::BindRepeating( |
| &SiteSettingsHandler:: |
| HandleUndoIgnoreOriginsForNotificationPermissionReview, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "isOriginValid", |
| base::BindRepeating(&SiteSettingsHandler::HandleIsOriginValid, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "isPatternValidForType", |
| base::BindRepeating(&SiteSettingsHandler::HandleIsPatternValidForType, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "updateIncognitoStatus", |
| base::BindRepeating(&SiteSettingsHandler::HandleUpdateIncognitoStatus, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "fetchZoomLevels", |
| base::BindRepeating(&SiteSettingsHandler::HandleFetchZoomLevels, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "removeZoomLevel", |
| base::BindRepeating(&SiteSettingsHandler::HandleRemoveZoomLevel, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "setBlockAutoplayEnabled", |
| base::BindRepeating(&SiteSettingsHandler::HandleSetBlockAutoplayEnabled, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "fetchBlockAutoplayStatus", |
| base::BindRepeating(&SiteSettingsHandler::HandleFetchBlockAutoplayStatus, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "clearEtldPlus1DataAndCookies", |
| base::BindRepeating( |
| &SiteSettingsHandler::HandleClearEtldPlus1DataAndCookies, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "recordAction", |
| base::BindRepeating(&SiteSettingsHandler::HandleRecordAction, |
| base::Unretained(this))); |
| web_ui()->RegisterMessageCallback( |
| "getNumCookiesString", |
| base::BindRepeating(&SiteSettingsHandler::HandleGetNumCookiesString, |
| base::Unretained(this))); |
| } |
| |
| void SiteSettingsHandler::OnJavascriptAllowed() { |
| ObserveSourcesForProfile(profile_); |
| if (profile_->HasPrimaryOTRProfile()) { |
| auto* primary_otr_profile = |
| profile_->GetPrimaryOTRProfile(/*create_if_needed=*/true); |
| // Avoid duplicate observation. |
| if (primary_otr_profile != profile_) |
| ObserveSourcesForProfile(primary_otr_profile); |
| } |
| |
| // Here we only subscribe to the HostZoomMap for the default storage partition |
| // since we don't allow the user to manage the zoom levels for apps. |
| // We're only interested in zoom-levels that are persisted, since the user |
| // is given the opportunity to view/delete these in the content-settings page. |
| host_zoom_map_subscription_ = |
| content::HostZoomMap::GetDefaultForBrowserContext(profile_) |
| ->AddZoomLevelChangedCallback( |
| base::BindRepeating(&SiteSettingsHandler::OnZoomLevelChanged, |
| base::Unretained(this))); |
| |
| pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>(); |
| pref_change_registrar_->Init(profile_->GetPrefs()); |
| |
| // If the block autoplay pref changes send the new status. |
| pref_change_registrar_->Add( |
| prefs::kBlockAutoplayEnabled, |
| base::BindRepeating(&SiteSettingsHandler::SendBlockAutoplayStatus, |
| base::Unretained(this))); |
| |
| // Listen for prefs that impact the effective cookie setting |
| pref_change_registrar_->Add( |
| prefs::kCookieControlsMode, |
| base::BindRepeating(&SiteSettingsHandler::SendCookieSettingDescription, |
| base::Unretained(this))); |
| } |
| |
| void SiteSettingsHandler::OnJavascriptDisallowed() { |
| observations_.RemoveAllObservations(); |
| chooser_observations_.RemoveAllObservations(); |
| host_zoom_map_subscription_ = {}; |
| pref_change_registrar_->Remove(prefs::kBlockAutoplayEnabled); |
| pref_change_registrar_->Remove(prefs::kCookieControlsMode); |
| observed_profiles_.RemoveAllObservations(); |
| } |
| |
| void SiteSettingsHandler::OnGetUsageInfo() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| // Site Details Page does not display the number of cookies for the origin. |
| const CookieTreeNode* root = cookies_tree_model_->GetRoot(); |
| int64_t size = 0; |
| std::string usage_string; |
| std::string cookie_string; |
| std::string fps_string; |
| bool fpsPolicy = false; |
| // Convert origin to hostname because CookieTreeNode use hostname as |
| // |title|(key). |
| // TODO(crbug.com/1415380): Ensure the key uniquely identifies the owner of |
| // the browsing data (hostname is insufficient) in CookieTreeModel or the new |
| // BrowsingDataModel. |
| std::string usage_hostname = GURL(usage_origin_).host(); |
| for (const auto& site : root->children()) { |
| std::string title = base::UTF16ToUTF8(site->GetTitle()); |
| if (title != usage_hostname) { |
| continue; |
| } |
| size += site->InclusiveSize(); |
| |
| // Usage info only includes unpartitioned cookies, so each cookie must be |
| // inspected. |
| // TODO (crbug.com/1271155): This is slow, the replacement for the |
| // CookiesTreeModel should improve this significantly. |
| int num_cookies = 0; |
| for (const auto& site_child : site->children()) { |
| if (site_child->GetDetailedInfo().node_type != |
| CookieTreeNode::DetailedInfo::TYPE_COOKIES) { |
| continue; |
| } |
| |
| num_cookies += base::ranges::count_if( |
| site_child->children(), |
| [](const std::unique_ptr<CookieTreeNode>& cookie) { |
| const auto& detailed_info = cookie->GetDetailedInfo(); |
| DCHECK(detailed_info.node_type == |
| CookieTreeNode::DetailedInfo::TYPE_COOKIE); |
| DCHECK(detailed_info.cookie); |
| return !detailed_info.cookie->IsPartitioned(); |
| }); |
| } |
| if (num_cookies != 0) { |
| cookie_string = base::UTF16ToUTF8(l10n_util::GetPluralStringFUTF16( |
| IDS_SETTINGS_SITE_SETTINGS_NUM_COOKIES, num_cookies)); |
| } |
| |
| auto* privacy_sandbox_service = |
| PrivacySandboxServiceFactory::GetForProfile(profile_); |
| auto fps_map = |
| GetFpsMap(privacy_sandbox_service, cookies_tree_model_.get()); |
| auto etld_plus1 = GetEtldPlusOne(site->GetDetailedInfo().origin); |
| if (fps_map.count(etld_plus1)) { |
| fps_string = |
| base::UTF16ToUTF8(base::i18n::MessageFormatter::FormatWithNamedArgs( |
| l10n_util::GetStringUTF16( |
| IDS_SETTINGS_SITE_SETTINGS_FIRST_PARTY_SETS_MEMBERSHIP_LABEL), |
| "MEMBERS", static_cast<int>(fps_map[etld_plus1].second), |
| "FPS_OWNER", fps_map[etld_plus1].first)); |
| fpsPolicy = privacy_sandbox_service->IsPartOfManagedFirstPartySet( |
| ConvertEtldToSchemefulSite(etld_plus1)); |
| } |
| break; |
| } |
| |
| for (const BrowsingDataModel::BrowsingDataEntryView& entry : |
| *browsing_data_model_) { |
| if (*entry.primary_host != usage_hostname) { |
| continue; |
| } |
| size += entry.data_details->storage_size; |
| } |
| |
| if (size > 0) { |
| usage_string = base::UTF16ToUTF8(ui::FormatBytes(size)); |
| } |
| |
| FireWebUIListener("usage-total-changed", base::Value(usage_origin_), |
| base::Value(usage_string), base::Value(cookie_string), |
| base::Value(fps_string), base::Value(fpsPolicy)); |
| } |
| |
| void SiteSettingsHandler::BrowsingDataModelCreated( |
| std::unique_ptr<BrowsingDataModel> model) { |
| browsing_data_model_ = std::move(model); |
| ModelBuilt(); |
| } |
| |
| void SiteSettingsHandler::OnContentSettingChanged( |
| const ContentSettingsPattern& primary_pattern, |
| const ContentSettingsPattern& secondary_pattern, |
| ContentSettingsType content_type) { |
| if (!site_settings::HasRegisteredGroupName(content_type)) |
| return; |
| |
| if (primary_pattern.MatchesAllHosts() && |
| secondary_pattern.MatchesAllHosts()) { |
| FireWebUIListener("contentSettingCategoryChanged", |
| base::Value(site_settings::ContentSettingsTypeToGroupName( |
| content_type))); |
| } else { |
| FireWebUIListener( |
| "contentSettingSitePermissionChanged", |
| base::Value( |
| site_settings::ContentSettingsTypeToGroupName(content_type)), |
| base::Value(primary_pattern.ToString()), |
| base::Value(secondary_pattern == ContentSettingsPattern::Wildcard() |
| ? "" |
| : secondary_pattern.ToString())); |
| } |
| |
| // If the default sound content setting changed then we should send block |
| // autoplay status. |
| if (primary_pattern.MatchesAllHosts() && |
| secondary_pattern.MatchesAllHosts() && |
| content_type == ContentSettingsType::SOUND) { |
| SendBlockAutoplayStatus(); |
| } |
| |
| // If the default cookie setting changed we should update the effective |
| // setting description. |
| if (content_type == ContentSettingsType::COOKIES) { |
| SendCookieSettingDescription(); |
| } |
| } |
| |
| void SiteSettingsHandler::OnOffTheRecordProfileCreated( |
| Profile* off_the_record) { |
| FireWebUIListener("onIncognitoStatusChanged", base::Value(true)); |
| ObserveSourcesForProfile(off_the_record); |
| } |
| |
| void SiteSettingsHandler::OnProfileWillBeDestroyed(Profile* profile) { |
| if (profile->IsOffTheRecord()) |
| FireWebUIListener("onIncognitoStatusChanged", base::Value(false)); |
| StopObservingSourcesForProfile(profile); |
| } |
| |
| void SiteSettingsHandler::OnObjectPermissionChanged( |
| absl::optional<ContentSettingsType> guard_content_settings_type, |
| ContentSettingsType data_content_settings_type) { |
| if (!guard_content_settings_type || |
| !site_settings::HasRegisteredGroupName(*guard_content_settings_type) || |
| !site_settings::HasRegisteredGroupName(data_content_settings_type)) { |
| return; |
| } |
| |
| FireWebUIListener("contentSettingChooserPermissionChanged", |
| base::Value(site_settings::ContentSettingsTypeToGroupName( |
| *guard_content_settings_type)), |
| base::Value(site_settings::ContentSettingsTypeToGroupName( |
| data_content_settings_type))); |
| } |
| |
| void SiteSettingsHandler::OnZoomLevelChanged( |
| const content::HostZoomMap::ZoomLevelChange& change) { |
| SendZoomLevels(); |
| } |
| |
| void SiteSettingsHandler::HandleFetchUsageTotal(const base::Value::List& args) { |
| AllowJavascript(); |
| CHECK_EQ(1U, args.size()); |
| usage_origin_ = args[0].GetString(); |
| |
| update_site_details_ = true; |
| RebuildModels(); |
| } |
| |
| void SiteSettingsHandler::HandleGetFpsMembershipLabel( |
| const base::Value::List& args) { |
| AllowJavascript(); |
| CHECK_EQ(3U, args.size()); |
| |
| std::string callback_id = args[0].GetString(); |
| int num_members = args[1].GetInt(); |
| std::string fps_owner = args[2].GetString(); |
| |
| const std::string label = |
| base::UTF16ToUTF8(base::i18n::MessageFormatter::FormatWithNamedArgs( |
| l10n_util::GetStringUTF16( |
| IDS_SETTINGS_SITE_SETTINGS_FIRST_PARTY_SETS_MEMBERSHIP_LABEL), |
| "MEMBERS", static_cast<int>(num_members), "FPS_OWNER", fps_owner)); |
| |
| ResolveJavascriptCallback(base::Value(callback_id), base::Value(label)); |
| } |
| |
| void SiteSettingsHandler::HandleClearUnpartitionedUsage( |
| const base::Value::List& args) { |
| CHECK_EQ(1U, args.size()); |
| const std::string& origin_string = args[0].GetString(); |
| auto origin = url::Origin::Create(GURL(origin_string)); |
| if (origin.opaque()) |
| return; |
| AllowJavascript(); |
| |
| // TODO(crbug.com/1368048): This code assumes that model pointers are valid. |
| // This assumption requires specific front-end behavior which is not strictly |
| // enforced. |
| DCHECK(browsing_data_model_); |
| DCHECK(cookies_tree_model_); |
| |
| RemoveMatchingNodes(cookies_tree_model_.get(), origin_string, absl::nullopt); |
| |
| // The scheme for some sites detail page is http on |
| // chrome://settings/content/all. Cookies or site data might not cleared if |
| // the existing cookie scheme was https when users click the site detail link |
| // to clear data. Hence, we need only additionally clear the HTTPS version if |
| // an origin scheme is HTTP. |
| std::vector<url::Origin> affected_origins = {origin}; |
| if (origin.GetURL().SchemeIs(url::kHttpScheme)) { |
| GURL https_url = origin.GetURL(); |
| GURL::Replacements replacements; |
| replacements.SetSchemeStr(url::kHttpsScheme); |
| https_url = https_url.ReplaceComponents(replacements); |
| auto https_origin = url::Origin::Create(https_url); |
| |
| // Also remove matching cookies node with HTTPS scheme if it exists to |
| // avoid confusion when cookies already exist when refreshing clear site |
| // data page. Notes: this also means HTTPS sites cookie will be cleared when |
| // user clear HTTP scheme Cookie. |
| RemoveMatchingNodes(cookies_tree_model_.get(), https_origin.GetURL().spec(), |
| absl::nullopt); |
| affected_origins.emplace_back(https_origin); |
| } |
| |
| RemoveNonTreeModelData(affected_origins); |
| } |
| |
| void SiteSettingsHandler::HandleClearPartitionedUsage( |
| const base::Value::List& args) { |
| CHECK_EQ(2U, args.size()); |
| const std::string& origin = args[0].GetString(); |
| const std::string& etld_plus1 = args[1].GetString(); |
| |
| RemoveMatchingNodes(cookies_tree_model_.get(), origin, etld_plus1); |
| } |
| |
| void SiteSettingsHandler::HandleSetDefaultValueForContentType( |
| const base::Value::List& args) { |
| CHECK_EQ(2U, args.size()); |
| const std::string& content_type = args[0].GetString(); |
| const std::string& setting = args[1].GetString(); |
| ContentSetting default_setting; |
| CHECK(content_settings::ContentSettingFromString(setting, &default_setting)); |
| ContentSettingsType type = |
| site_settings::ContentSettingsTypeFromGroupName(content_type); |
| |
| Profile* profile = profile_; |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| // ChromeOS special case: in Guest mode, settings are opened in Incognito |
| // mode so we need the original profile to actually modify settings. |
| if (user_manager::UserManager::Get()->IsLoggedInAsGuest()) |
| profile = profile->GetOriginalProfile(); |
| #endif |
| HostContentSettingsMap* map = |
| HostContentSettingsMapFactory::GetForProfile(profile); |
| ContentSetting previous_setting = |
| map->GetDefaultContentSetting(type, nullptr); |
| map->SetDefaultContentSetting(type, default_setting); |
| |
| if (type == ContentSettingsType::SOUND && |
| previous_setting != default_setting) { |
| if (default_setting == CONTENT_SETTING_BLOCK) { |
| base::RecordAction( |
| base::UserMetricsAction("SoundContentSetting.MuteBy.DefaultSwitch")); |
| } else { |
| base::RecordAction(base::UserMetricsAction( |
| "SoundContentSetting.UnmuteBy.DefaultSwitch")); |
| } |
| } |
| } |
| |
| void SiteSettingsHandler::HandleGetDefaultValueForContentType( |
| const base::Value::List& args) { |
| AllowJavascript(); |
| |
| CHECK_EQ(2U, args.size()); |
| const base::Value& callback_id = args[0]; |
| const std::string& type = args[1].GetString(); |
| |
| ContentSettingsType content_type = |
| site_settings::ContentSettingsTypeFromGroupName(type); |
| HostContentSettingsMap* map = |
| HostContentSettingsMapFactory::GetForProfile(profile_); |
| |
| base::Value::Dict category; |
| site_settings::GetContentCategorySetting(map, content_type, &category); |
| ResolveJavascriptCallback(callback_id, category); |
| } |
| |
| void SiteSettingsHandler::HandleGetAllSites(const base::Value::List& args) { |
| AllowJavascript(); |
| |
| request_started_time_ = base::TimeTicks::Now(); |
| |
| CHECK_EQ(1U, args.size()); |
| std::string callback_id = args[0].GetString(); |
| |
| all_sites_map_.clear(); |
| origin_permission_set_.clear(); |
| |
| // Incognito contains incognito content settings plus non-incognito content |
| // settings. Thus if it exists, just get exceptions for the incognito profile. |
| Profile* profile = profile_; |
| if (profile_->HasPrimaryOTRProfile() && |
| profile_->GetPrimaryOTRProfile(/*create_if_needed=*/true) != profile_) { |
| profile = profile_->GetPrimaryOTRProfile(/*create_if_needed=*/true); |
| } |
| DCHECK(profile); |
| HostContentSettingsMap* map = |
| HostContentSettingsMapFactory::GetForProfile(profile); |
| |
| std::vector<ContentSettingsType> content_types = |
| site_settings::GetVisiblePermissionCategories(); |
| // Make sure to include cookies, because All Sites handles data storage |
| // cookies as well as regular ContentSettingsTypes. |
| content_types.push_back(ContentSettingsType::COOKIES); |
| |
| // Retrieve a list of embargoed settings to check separately. This ensures |
| // that only settings included in |content_types| will be listed in all sites. |
| auto* autoblocker = |
| PermissionDecisionAutoBlockerFactory::GetForProfile(profile_); |
| for (auto& url : autoblocker->GetEmbargoedOrigins(content_types)) { |
| // Add |url| to the set if there are any embargo settings. |
| auto origin = url::Origin::Create(url); |
| InsertOriginIntoGroup(&all_sites_map_, origin); |
| origin_permission_set_.insert(origin); |
| } |
| |
| // Get permission exceptions which apply to a single site |
| for (auto content_type : content_types) { |
| auto exceptions = site_settings::GetSingleOriginExceptionsForContentType( |
| map, content_type); |
| for (const auto& e : exceptions) { |
| auto origin = url::Origin::Create(GURL(e.primary_pattern.ToString())); |
| InsertOriginIntoGroup(&all_sites_map_, origin); |
| origin_permission_set_.insert(origin); |
| } |
| } |
| |
| // Get device chooser permission exceptions. |
| for (auto content_type : kChooserDataContentSettingsTypes) { |
| base::StringPiece group_name = |
| site_settings::ContentSettingsTypeToGroupName(content_type); |
| DCHECK(!group_name.empty()); |
| const site_settings::ChooserTypeNameEntry* chooser_type = |
| site_settings::ChooserTypeFromGroupName(group_name); |
| DCHECK(chooser_type); |
| base::Value::List exceptions = |
| site_settings::GetChooserExceptionListFromProfile(profile_, |
| *chooser_type); |
| for (const base::Value& exception : exceptions) { |
| const base::Value::List* sites = |
| exception.GetDict().FindList(site_settings::kSites); |
| DCHECK(sites); |
| for (const base::Value& site : *sites) { |
| const std::string* origin_string = |
| site.GetDict().FindString(site_settings::kOrigin); |
| DCHECK(origin_string); |
| auto origin = url::Origin::Create(GURL(*origin_string)); |
| InsertOriginIntoGroup(&all_sites_map_, origin); |
| origin_permission_set_.insert(origin); |
| } |
| } |
| } |
| |
| // Recreate the cookies tree model to refresh the usage information. |
| // This happens in the background and will call TreeModelEndBatch() when |
| // finished. At that point we send usage data to the page. |
| send_sites_list_ = true; |
| RebuildModels(); |
| |
| base::Value::List result; |
| |
| // Respond with currently available data. |
| ConvertSiteGroupMapToList(all_sites_map_, origin_permission_set_, &result, |
| profile, cookies_tree_model_.get()); |
| |
| LogAllSitesAction(AllSitesAction2::kLoadPage); |
| |
| ResolveJavascriptCallback(base::Value(callback_id), result); |
| } |
| |
| void SiteSettingsHandler::HandleGetCategoryList(const base::Value::List& args) { |
| AllowJavascript(); |
| |
| CHECK_EQ(2U, args.size()); |
| std::string callback_id = args[0].GetString(); |
| GURL origin(args[1].GetString()); |
| |
| base::Value::List result; |
| for (ContentSettingsType content_type : |
| site_settings::GetVisiblePermissionCategories()) { |
| result.Append(site_settings::ContentSettingsTypeToGroupName(content_type)); |
| } |
| |
| ResolveJavascriptCallback(base::Value(callback_id), result); |
| } |
| |
| void SiteSettingsHandler::HandleGetCookieSettingDescription( |
| const base::Value::List& args) { |
| AllowJavascript(); |
| CHECK_EQ(1U, args.size()); |
| std::string callback_id = args[0].GetString(); |
| ResolveJavascriptCallback(base::Value(callback_id), |
| base::Value(GetCookieSettingDescription(profile_))); |
| } |
| |
| void SiteSettingsHandler::HandleGetRecentSitePermissions( |
| const base::Value::List& args) { |
| AllowJavascript(); |
| |
| CHECK_EQ(2U, args.size()); |
| std::string callback_id = args[0].GetString(); |
| size_t max_sources = base::checked_cast<size_t>(args[1].GetInt()); |
| |
| const std::vector<ContentSettingsType>& content_types = |
| site_settings::GetVisiblePermissionCategories(); |
| auto recent_site_permissions = site_settings::GetRecentSitePermissions( |
| profile_, content_types, max_sources); |
| |
| // Convert groups of TimestampedPermissions for consumption by JS |
| base::Value::List result; |
| for (const auto& site_permissions : recent_site_permissions) { |
| DCHECK(!site_permissions.settings.empty()); |
| base::Value::Dict recent_site; |
| recent_site.Set(site_settings::kOrigin, site_permissions.origin.spec()); |
| recent_site.Set(site_settings::kDisplayName, site_permissions.display_name); |
| recent_site.Set(site_settings::kIncognito, site_permissions.incognito); |
| |
| base::Value::List permissions_list; |
| for (const auto& p : site_permissions.settings) { |
| base::Value::Dict recent_permission; |
| recent_permission.Set( |
| site_settings::kType, |
| |
| site_settings::ContentSettingsTypeToGroupName(p.content_type)); |
| recent_permission.Set( |
| site_settings::kSetting, |
| |
| content_settings::ContentSettingToString(p.content_setting)); |
| recent_permission.Set( |
| site_settings::kSource, |
| |
| site_settings::SiteSettingSourceToString(p.setting_source)); |
| permissions_list.Append(std::move(recent_permission)); |
| } |
| recent_site.Set(site_settings::kRecentPermissions, |
| std::move(permissions_list)); |
| result.Append(std::move(recent_site)); |
| } |
| ResolveJavascriptCallback(base::Value(callback_id), result); |
| } |
| |
| base::Value::List SiteSettingsHandler::PopulateCookiesAndUsageData( |
| Profile* profile) { |
| std::map<url::Origin, int64_t> origin_size_map; |
| std::map<std::pair<std::string, absl::optional<std::string>>, int> |
| host_cookie_map; |
| base::Value::List list_value; |
| |
| GetOriginStorage(&all_sites_map_, &origin_size_map); |
| GetHostCookies(&all_sites_map_, &host_cookie_map); |
| ConvertSiteGroupMapToList(all_sites_map_, origin_permission_set_, &list_value, |
| profile, cookies_tree_model_.get()); |
| |
| // Merge the origin usage and cookies number into |list_value|. |
| for (base::Value& item : list_value) { |
| base::Value::Dict& site_group = item.GetDict(); |
| base::Value::List& origin_list = *site_group.FindList(kOriginList); |
| int cookie_num = 0; |
| const std::string& etld_plus1 = |
| *site_group.FindString(kEffectiveTopLevelDomainPlus1Name); |
| const auto& etld_plus1_cookie_num_it = |
| host_cookie_map.find({etld_plus1, absl::nullopt}); |
| // Add the number of eTLD+1 scoped cookies. |
| if (etld_plus1_cookie_num_it != host_cookie_map.end()) { |
| cookie_num = etld_plus1_cookie_num_it->second; |
| } |
| // Iterate over the origins for the ETLD+1, and set their usage and cookie |
| // numbers. |
| for (base::Value& value : origin_list) { |
| base::Value::Dict& origin_info = value.GetDict(); |
| auto origin = |
| url::Origin::Create(GURL(*origin_info.FindString("origin"))); |
| bool is_partitioned = |
| origin_info.FindBool("isPartitioned").value_or(false); |
| if (!is_partitioned) { |
| // Only unpartitioned storage has a size. |
| const auto& size_info_it = origin_size_map.find(origin); |
| if (size_info_it != origin_size_map.end()) |
| origin_info.Set("usage", static_cast<double>(size_info_it->second)); |
| } |
| const auto& host_cookie_num_it = host_cookie_map.find( |
| {origin.host(), |
| (is_partitioned ? absl::optional<std::string>(etld_plus1) |
| : absl::nullopt)}); |
| if (host_cookie_num_it != host_cookie_map.end()) { |
| origin_info.Set(kNumCookies, host_cookie_num_it->second); |
| // Add cookies numbers for origins that isn't an eTLD+1. |
| if (origin.host() != etld_plus1 || is_partitioned) { |
| cookie_num += host_cookie_num_it->second; |
| } |
| } |
| } |
| site_group.Set(kNumCookies, cookie_num); |
| } |
| return list_value; |
| } |
| |
| void SiteSettingsHandler::OnStorageFetched() { |
| AllowJavascript(); |
| |
| // Record how long does it take to fetch the storage and return complete |
| // information to the UI. |
| DCHECK(!request_started_time_.is_null()); |
| base::UmaHistogramTimes("WebsiteSettings.GetAllSitesLoadTime", |
| base::TimeTicks::Now() - request_started_time_); |
| FireWebUIListener("onStorageListFetched", |
| PopulateCookiesAndUsageData(profile_)); |
| } |
| |
| void SiteSettingsHandler::HandleGetFormattedBytes( |
| const base::Value::List& args) { |
| AllowJavascript(); |
| CHECK_EQ(2U, args.size()); |
| int64_t num_bytes = static_cast<int64_t>(args[1].GetDouble()); |
| ResolveJavascriptCallback(/*callback_id=*/args[0], |
| base::Value(ui::FormatBytes(num_bytes))); |
| } |
| |
| void SiteSettingsHandler::HandleGetExceptionList( |
| const base::Value::List& args) { |
| AllowJavascript(); |
| |
| CHECK_EQ(2U, args.size()); |
| const base::Value& callback_id = args[0]; |
| const std::string& type = args[1].GetString(); |
| ContentSettingsType content_type = |
| site_settings::ContentSettingsTypeFromGroupName(type); |
| |
| base::Value::List exceptions; |
| |
| AddExceptionsGrantedByHostedApps(profile_, APIPermissionFromGroupName(type), |
| &exceptions); |
| site_settings::GetExceptionsForContentType(content_type, profile_, web_ui(), |
| /*incognito=*/false, &exceptions); |
| |
| Profile* incognito = |
| profile_->HasPrimaryOTRProfile() |
| ? profile_->GetPrimaryOTRProfile(/*create_if_needed=*/true) |
| : nullptr; |
| // On Chrome OS in Guest mode the incognito profile is the primary profile, |
| // so do not fetch an extra copy of the same exceptions. |
| if (incognito && incognito != profile_) { |
| site_settings::GetExceptionsForContentType(content_type, incognito, |
| web_ui(), |
| /*incognito=*/true, &exceptions); |
| } |
| |
| ResolveJavascriptCallback(callback_id, exceptions); |
| } |
| |
| void SiteSettingsHandler::HandleGetChooserExceptionList( |
| const base::Value::List& args) { |
| AllowJavascript(); |
| |
| CHECK_EQ(2U, args.size()); |
| const base::Value& callback_id = args[0]; |
| const std::string& type = args[1].GetString(); |
| const site_settings::ChooserTypeNameEntry* chooser_type = |
| site_settings::ChooserTypeFromGroupName(type); |
| CHECK(chooser_type); |
| |
| base::Value::List exceptions = |
| site_settings::GetChooserExceptionListFromProfile(profile_, |
| *chooser_type); |
| ResolveJavascriptCallback(callback_id, exceptions); |
| } |
| |
| void SiteSettingsHandler::HandleGetOriginPermissions( |
| const base::Value::List& args) { |
| AllowJavascript(); |
| |
| CHECK_EQ(3U, args.size()); |
| const base::Value& callback_id = args[0]; |
| std::string origin = args[1].GetString(); |
| const base::Value::List& types = args[2].GetList(); |
| |
| // Note: Invalid URLs will just result in default settings being shown. |
| const GURL origin_url(origin); |
| base::Value::List exceptions; |
| for (const auto& type_val : types) { |
| std::string type; |
| DCHECK(type_val.is_string()); |
| const std::string* maybe_type = type_val.GetIfString(); |
| if (maybe_type) |
| type = *maybe_type; |
| ContentSettingsType content_type = |
| site_settings::ContentSettingsTypeFromGroupName(type); |
| CHECK(content_type != ContentSettingsType::DEFAULT) |
| << type << " is not expected to have a UI representation."; |
| HostContentSettingsMap* map = |
| HostContentSettingsMapFactory::GetForProfile(profile_); |
| |
| std::string source_string; |
| ContentSetting content_setting = site_settings::GetContentSettingForOrigin( |
| profile_, map, origin_url, content_type, &source_string); |
| std::string content_setting_string = |
| content_settings::ContentSettingToString(content_setting); |
| |
| base::Value::Dict raw_site_exception; |
| raw_site_exception.Set(site_settings::kEmbeddingOrigin, origin); |
| raw_site_exception.Set(site_settings::kIncognito, |
| profile_->IsOffTheRecord()); |
| raw_site_exception.Set(site_settings::kOrigin, origin); |
| raw_site_exception.Set(site_settings::kSetting, content_setting_string); |
| raw_site_exception.Set(site_settings::kSource, source_string); |
| |
| UrlIdentity identity = site_settings::GetUrlIdentityForGURL( |
| profile_, origin_url, /*hostname_only=*/false); |
| std::string display_name; |
| if (identity.type == UrlIdentity::Type::kChromeExtension || |
| identity.type == UrlIdentity::Type::kIsolatedWebApp) { |
| // Append " (ID: <id>)" to extensions and IWA names as the user could have |
| // multiple extensions/IWAs installed with the same name. |
| display_name = l10n_util::GetStringFUTF8( |
| IDS_SETTINGS_EXTENSION_OR_APP_DISPLAY_NAME, identity.name, |
| base::UTF8ToUTF16(origin_url.host_piece())); |
| } else { |
| display_name = base::UTF16ToUTF8(identity.name); |
| } |
| raw_site_exception.Set(site_settings::kDisplayName, display_name); |
| |
| exceptions.Append(std::move(raw_site_exception)); |
| } |
| |
| ResolveJavascriptCallback(callback_id, exceptions); |
| } |
| |
| void SiteSettingsHandler::HandleGetNotificationPermissionReviewList( |
| const base::Value::List& args) { |
| AllowJavascript(); |
| |
| const base::Value& callback_id = args[0]; |
| |
| base::Value::List result = PopulateNotificationPermissionReviewData(); |
| |
| ResolveJavascriptCallback(callback_id, base::Value(std::move(result))); |
| } |
| |
| void SiteSettingsHandler::HandleGetFileSystemGrants( |
| const base::Value::List& args) { |
| CHECK_EQ(1U, args.size()); |
| AllowJavascript(); |
| |
| const base::Value& callback_id = args[0]; |
| base::Value::List grants = PopulateFileSystemGrantData(); |
| |
| ResolveJavascriptCallback(callback_id, grants); |
| } |
| |
| void SiteSettingsHandler::HandleRevokeFileSystemGrant( |
| const base::Value::List& args) { |
| // TODO(crbug.com/1373962): Remove feature flag check after persisted |
| // permissions is fully launched. |
| DCHECK(base::FeatureList::IsEnabled( |
| features::kFileSystemAccessPersistentPermissions)); |
| CHECK_EQ(2U, args.size()); |
| AllowJavascript(); |
| |
| auto url = GURL(args[0].GetString()); |
| DCHECK(url.is_valid()); |
| const url::Origin& origin = url::Origin::Create(url); |
| |
| const base::FilePath& file_path = |
| storage::StringToFilePath(args[1].GetString()); |
| |
| ChromeFileSystemAccessPermissionContext* permission_context = |
| FileSystemAccessPermissionContextFactory::GetForProfile(profile_); |
| |
| permission_context->RevokeGrant( |
| origin, file_path, |
| ChromeFileSystemAccessPermissionContext::PersistedPermissionOptions:: |
| kUpdatePersistedPermission); |
| } |
| |
| void SiteSettingsHandler::HandleRevokeFileSystemGrants( |
| const base::Value::List& args) { |
| // TODO(crbug.com/1373962): Remove feature flag check after persisted |
| // permissions is fully launched. |
| DCHECK(base::FeatureList::IsEnabled( |
| features::kFileSystemAccessPersistentPermissions)); |
| |
| CHECK_EQ(1U, args.size()); |
| AllowJavascript(); |
| |
| auto url = GURL(args[0].GetString()); |
| DCHECK(url.is_valid()); |
| const url::Origin& origin = url::Origin::Create(url); |
| |
| ChromeFileSystemAccessPermissionContext* permission_context = |
| FileSystemAccessPermissionContextFactory::GetForProfile(profile_); |
| |
| permission_context->RevokeGrants( |
| origin, ChromeFileSystemAccessPermissionContext:: |
| PersistedPermissionOptions::kUpdatePersistedPermission); |
| } |
| |
| void SiteSettingsHandler::HandleSetOriginPermissions( |
| const base::Value::List& args) { |
| CHECK_EQ(3U, args.size()); |
| const std::string& origin_string = args[0].GetString(); |
| const std::string* type_string = args[1].GetIfString(); |
| std::string value = args[2].GetString(); |
| |
| const GURL origin(origin_string); |
| if (!origin.is_valid()) |
| return; |
| |
| ContentSetting setting; |
| CHECK(content_settings::ContentSettingFromString(value, &setting)); |
| std::vector<ContentSettingsType> types; |
| if (type_string) { |
| ContentSettingsType content_type = |
| site_settings::ContentSettingsTypeFromGroupName(*type_string); |
| CHECK(content_type != ContentSettingsType::DEFAULT) |
| << *type_string << " is not expected to have a UI representation."; |
| types.push_back(content_type); |
| } else { |
| // Clear device chooser data permission exceptions. |
| if (setting == CONTENT_SETTING_DEFAULT) { |
| for (auto content_type : kChooserDataContentSettingsTypes) { |
| base::StringPiece group_name = |
| site_settings::ContentSettingsTypeToGroupName(content_type); |
| DCHECK(!group_name.empty()); |
| const site_settings::ChooserTypeNameEntry* chooser_type = |
| site_settings::ChooserTypeFromGroupName(group_name); |
| DCHECK(chooser_type); |
| |
| // The BluetoothChooserContext is only available when the |
| // WebBluetoothNewPermissionsBackend flag is enabled. |
| // TODO(crbug.com/589228): Remove the nullptr check when it is enabled |
| // by default. |
| permissions::ObjectPermissionContextBase* chooser_context = |
| chooser_type->get_context(profile_); |
| if (!chooser_context) { |
| continue; |
| } |
| |
| auto objects = chooser_context->GetAllGrantedObjects(); |
| for (const auto& object : objects) { |
| if (origin == object->origin) { |
| chooser_context->RevokeObjectPermission(url::Origin::Create(origin), |
| object->value); |
| } |
| } |
| } |
| } |
| |
| types = site_settings::GetVisiblePermissionCategories(); |
| } |
| |
| HostContentSettingsMap* map = |
| HostContentSettingsMapFactory::GetForProfile(profile_); |
| for (ContentSettingsType content_type : types) { |
| permissions::PermissionUmaUtil::ScopedRevocationReporter |
| scoped_revocation_reporter( |
| profile_, origin, origin, content_type, |
| permissions::PermissionSourceUI::SITE_SETTINGS); |
| |
| // Clear any existing embargo status if the new setting isn't block. |
| if (setting != CONTENT_SETTING_BLOCK) { |
| PermissionDecisionAutoBlockerFactory::GetForProfile(profile_) |
| ->RemoveEmbargoAndResetCounts(origin, content_type); |
| } |
| map->SetContentSettingDefaultScope(origin, origin, content_type, setting); |
| if (content_type == ContentSettingsType::SOUND) { |
| ContentSetting default_setting = |
| map->GetDefaultContentSetting(ContentSettingsType::SOUND, nullptr); |
| bool mute = (setting == CONTENT_SETTING_BLOCK) || |
| (setting == CONTENT_SETTING_DEFAULT && |
| default_setting == CONTENT_SETTING_BLOCK); |
| if (mute) { |
| base::RecordAction( |
| base::UserMetricsAction("SoundContentSetting.MuteBy.SiteSettings")); |
| } else { |
| base::RecordAction(base::UserMetricsAction( |
| "SoundContentSetting.UnmuteBy.SiteSettings")); |
| } |
| } |
| } |
| |
| // Show an infobar reminding the user to reload tabs where their site |
| // permissions have been updated. |
| // Info bar should only be shown on pages with the same origin and |
| // on the same profile |
| for (auto* it : *BrowserList::GetInstance()) { |
| TabStripModel* tab_strip = it->tab_strip_model(); |
| for (int i = 0; i < tab_strip->count(); ++i) { |
| content::WebContents* web_contents = tab_strip->GetWebContentsAt(i); |
| GURL tab_url = web_contents->GetLastCommittedURL(); |
| if (url::IsSameOriginWith(origin, tab_url) && |
| it->profile()->GetOriginalProfile() == |
| profile_->GetOriginalProfile()) { |
| infobars::ContentInfoBarManager* infobar_manager = |
| infobars::ContentInfoBarManager::FromWebContents(web_contents); |
| PageInfoInfoBarDelegate::Create(infobar_manager); |
| } |
| } |
| } |
| } |
| |
| void SiteSettingsHandler::HandleResetCategoryPermissionForPattern( |
| const base::Value::List& args) { |
| CHECK_EQ(4U, args.size()); |
| const std::string& primary_pattern_string = args[0].GetString(); |
| const std::string& secondary_pattern_string = args[1].GetString(); |
| const std::string& type = args[2].GetString(); |
| const bool& incognito = args[3].GetBool(); |
| |
| ContentSettingsType content_type = |
| site_settings::ContentSettingsTypeFromGroupName(type); |
| CHECK(content_type != ContentSettingsType::DEFAULT) |
| << type << " is not expected to have a UI representation."; |
| |
| Profile* profile = nullptr; |
| if (incognito) { |
| if (!profile_->HasPrimaryOTRProfile()) |
| return; |
| profile = profile_->GetPrimaryOTRProfile(/*create_if_needed=*/true); |
| } else { |
| profile = profile_; |
| } |
| |
| HostContentSettingsMap* map = |
| HostContentSettingsMapFactory::GetForProfile(profile); |
| |
| ContentSettingsPattern primary_pattern = |
| ContentSettingsPattern::FromString(primary_pattern_string); |
| ContentSettingsPattern secondary_pattern = |
| secondary_pattern_string.empty() |
| ? ContentSettingsPattern::Wildcard() |
| : ContentSettingsPattern::FromString(secondary_pattern_string); |
| permissions::PermissionUmaUtil::ScopedRevocationReporter |
| scoped_revocation_reporter( |
| profile, primary_pattern, secondary_pattern, content_type, |
| permissions::PermissionSourceUI::SITE_SETTINGS); |
| |
| map->SetContentSettingCustomScope(primary_pattern, secondary_pattern, |
| content_type, CONTENT_SETTING_DEFAULT); |
| |
| if (content_type == ContentSettingsType::SOUND) { |
| ContentSetting default_setting = |
| map->GetDefaultContentSetting(ContentSettingsType::SOUND, nullptr); |
| if (default_setting == CONTENT_SETTING_BLOCK) { |
| base::RecordAction(base::UserMetricsAction( |
| "SoundContentSetting.MuteBy.PatternException")); |
| } else { |
| base::RecordAction(base::UserMetricsAction( |
| "SoundContentSetting.UnmuteBy.PatternException")); |
| } |
| } |
| |
| // End embargo if currently active. |
| auto url = GURL(primary_pattern_string); |
| if (url.is_valid()) { |
| PermissionDecisionAutoBlockerFactory::GetForProfile(profile) |
| ->RemoveEmbargoAndResetCounts(url, content_type); |
| } |
| |
| if (content_type == ContentSettingsType::NOTIFICATIONS) { |
| SendNotificationPermissionReviewList(); |
| } |
| } |
| |
| void SiteSettingsHandler::HandleSetCategoryPermissionForPattern( |
| const base::Value::List& args) { |
| CHECK_EQ(5U, args.size()); |
| const std::string& primary_pattern_string = args[0].GetString(); |
| const std::string& secondary_pattern_string = args[1].GetString(); |
| const std::string& type = args[2].GetString(); |
| const std::string& value = args[3].GetString(); |
| const bool& incognito = args[4].GetBool(); |
| |
| ContentSettingsType content_type = |
| site_settings::ContentSettingsTypeFromGroupName(type); |
| CHECK(content_type != ContentSettingsType::DEFAULT) |
| << type << " is not expected to have a UI representation."; |
| ContentSetting setting; |
| CHECK(content_settings::ContentSettingFromString(value, &setting)); |
| |
| Profile* target_profile = nullptr; |
| if (incognito) { |
| if (!profile_->HasPrimaryOTRProfile()) |
| return; |
| target_profile = profile_->GetPrimaryOTRProfile(/*create_if_needed=*/true); |
| } else { |
| target_profile = profile_; |
| } |
| |
| HostContentSettingsMap* map = |
| HostContentSettingsMapFactory::GetForProfile(target_profile); |
| |
| ContentSettingsPattern primary_pattern = |
| ContentSettingsPattern::FromString(primary_pattern_string); |
| ContentSettingsPattern secondary_pattern = |
| secondary_pattern_string.empty() |
| ? ContentSettingsPattern::Wildcard() |
| : ContentSettingsPattern::FromString(secondary_pattern_string); |
| |
| // Clear any existing embargo status if the new setting isn't block. |
| if (setting != CONTENT_SETTING_BLOCK) { |
| GURL url(primary_pattern.ToString()); |
| if (url.is_valid()) { |
| PermissionDecisionAutoBlockerFactory::GetForProfile(target_profile) |
| ->RemoveEmbargoAndResetCounts(url, content_type); |
| } |
| } |
| |
| permissions::PermissionUmaUtil::ScopedRevocationReporter |
| scoped_revocation_reporter( |
| target_profile, primary_pattern, secondary_pattern, content_type, |
| permissions::PermissionSourceUI::SITE_SETTINGS); |
| |
| map->SetContentSettingCustomScope(primary_pattern, secondary_pattern, |
| content_type, setting); |
| |
| if (content_type == ContentSettingsType::SOUND) { |
| ContentSetting default_setting = |
| map->GetDefaultContentSetting(ContentSettingsType::SOUND, nullptr); |
| bool mute = (setting == CONTENT_SETTING_BLOCK) || |
| (setting == CONTENT_SETTING_DEFAULT && |
| default_setting == CONTENT_SETTING_BLOCK); |
| if (mute) { |
| base::RecordAction(base::UserMetricsAction( |
| "SoundContentSetting.MuteBy.PatternException")); |
| } else { |
| base::RecordAction(base::UserMetricsAction( |
| "SoundContentSetting.UnmuteBy.PatternException")); |
| } |
| } |
| |
| if (content_type == ContentSettingsType::NOTIFICATIONS) { |
| SendNotificationPermissionReviewList(); |
| } |
| } |
| |
| void SiteSettingsHandler::HandleResetChooserExceptionForSite( |
| const base::Value::List& args) { |
| CHECK_EQ(3U, args.size()); |
| |
| const std::string& chooser_type_str = args[0].GetString(); |
| const site_settings::ChooserTypeNameEntry* chooser_type = |
| site_settings::ChooserTypeFromGroupName(chooser_type_str); |
| CHECK(chooser_type); |
| |
| const std::string& origin_str = args[1].GetString(); |
| GURL origin(origin_str); |
| CHECK(origin.is_valid()); |
| |
| permissions::ObjectPermissionContextBase* chooser_context = |
| chooser_type->get_context(profile_); |
| chooser_context->RevokeObjectPermission(url::Origin::Create(origin), |
| args[2].GetDict()); |
| } |
| |
| void SiteSettingsHandler::HandleIgnoreOriginsForNotificationPermissionReview( |
| const base::Value::List& args) { |
| CHECK_EQ(1U, args.size()); |
| const base::Value::List& origins = args[0].GetList(); |
| |
| auto* service = |
| NotificationPermissionsReviewServiceFactory::GetForProfile(profile_); |
| DCHECK(service); |
| |
| for (const auto& origin : origins) { |
| const ContentSettingsPattern primary_pattern = |
| ContentSettingsPattern::FromString(origin.GetString()); |
| service->AddPatternToNotificationPermissionReviewBlocklist( |
| primary_pattern, ContentSettingsPattern::Wildcard()); |
| } |
| |
| SendNotificationPermissionReviewList(); |
| } |
| |
| void SiteSettingsHandler::HandleResetNotificationPermissionForOrigins( |
| const base::Value::List& args) { |
| CHECK_EQ(1U, args.size()); |
| |
| const base::Value::List& origins = args[0].GetList(); |
| |
| HostContentSettingsMap* map = |
| HostContentSettingsMapFactory::GetForProfile(profile_); |
| |
| for (const auto& origin : origins) { |
| map->SetContentSettingCustomScope( |
| ContentSettingsPattern::FromString(origin.GetString()), |
| ContentSettingsPattern::Wildcard(), ContentSettingsType::NOTIFICATIONS, |
| CONTENT_SETTING_DEFAULT); |
| } |
| |
| SendNotificationPermissionReviewList(); |
| } |
| |
| void SiteSettingsHandler::HandleBlockNotificationPermissionForOrigins( |
| const base::Value::List& args) { |
| CHECK_EQ(1U, args.size()); |
| const base::Value::List& origins = args[0].GetList(); |
| |
| HostContentSettingsMap* map = |
| HostContentSettingsMapFactory::GetForProfile(profile_); |
| for (const auto& origin : origins) { |
| map->SetContentSettingCustomScope( |
| ContentSettingsPattern::FromString(origin.GetString()), |
| ContentSettingsPattern::Wildcard(), ContentSettingsType::NOTIFICATIONS, |
| CONTENT_SETTING_BLOCK); |
| } |
| |
| SendNotificationPermissionReviewList(); |
| } |
| |
| void SiteSettingsHandler::HandleAllowNotificationPermissionForOrigins( |
| const base::Value::List& args) { |
| CHECK_EQ(1U, args.size()); |
| const base::Value::List& origins = args[0].GetList(); |
| |
| HostContentSettingsMap* map = |
| HostContentSettingsMapFactory::GetForProfile(profile_); |
| |
| for (const auto& origin : origins) { |
| map->SetContentSettingCustomScope( |
| ContentSettingsPattern::FromString(origin.GetString()), |
| ContentSettingsPattern::Wildcard(), ContentSettingsType::NOTIFICATIONS, |
| CONTENT_SETTING_ALLOW); |
| } |
| |
| SendNotificationPermissionReviewList(); |
| } |
| |
| void SiteSettingsHandler:: |
| HandleUndoIgnoreOriginsForNotificationPermissionReview( |
| const base::Value::List& args) { |
| CHECK_EQ(1U, args.size()); |
| const base::Value::List& origins = args[0].GetList(); |
| auto* service = |
| NotificationPermissionsReviewServiceFactory::GetForProfile(profile_); |
| DCHECK(service); |
| |
| for (const auto& origin : origins) { |
| const ContentSettingsPattern& primary_pattern = |
| ContentSettingsPattern::FromString(origin.GetString()); |
| service->RemovePatternFromNotificationPermissionReviewBlocklist( |
| primary_pattern, ContentSettingsPattern::Wildcard()); |
| } |
| SendNotificationPermissionReviewList(); |
| } |
| |
| void SiteSettingsHandler::HandleIsOriginValid(const base::Value::List& args) { |
| AllowJavascript(); |
| CHECK_EQ(2U, args.size()); |
| const base::Value& callback_id = args[0]; |
| const std::string& origin_string = args[1].GetString(); |
| |
| ResolveJavascriptCallback(callback_id, |
| base::Value(GURL(origin_string).is_valid())); |
| } |
| |
| void SiteSettingsHandler::HandleIsPatternValidForType( |
| const base::Value::List& args) { |
| AllowJavascript(); |
| CHECK_EQ(3U, args.size()); |
| const base::Value& callback_id = args[0]; |
| const std::string& pattern_string = args[1].GetString(); |
| const std::string& type_string = args[2].GetString(); |
| |
| ContentSettingsType content_type = |
| site_settings::ContentSettingsTypeFromGroupName(type_string); |
| CHECK(content_type != ContentSettingsType::DEFAULT) |
| << type_string << " is not expected to have a UI representation."; |
| |
| std::string reason; |
| bool is_valid = |
| IsPatternValidForType(pattern_string, content_type, profile_, &reason); |
| |
| base::Value::Dict return_value; |
| return_value.Set(kIsValidKey, base::Value(is_valid)); |
| return_value.Set(kReasonKey, base::Value(std::move(reason))); |
| ResolveJavascriptCallback(callback_id, return_value); |
| } |
| |
| void SiteSettingsHandler::HandleUpdateIncognitoStatus( |
| const base::Value::List& args) { |
| AllowJavascript(); |
| FireWebUIListener("onIncognitoStatusChanged", |
| base::Value(profile_->HasPrimaryOTRProfile())); |
| } |
| |
| void SiteSettingsHandler::HandleFetchZoomLevels(const base::Value::List& args) { |
| AllowJavascript(); |
| SendZoomLevels(); |
| } |
| |
| void SiteSettingsHandler::SendZoomLevels() { |
| if (!IsJavascriptAllowed()) |
| return; |
| |
| base::Value::List zoom_levels_exceptions; |
| |
| content::HostZoomMap* host_zoom_map = |
| content::HostZoomMap::GetDefaultForBrowserContext(profile_); |
| content::HostZoomMap::ZoomLevelVector zoom_levels( |
| host_zoom_map->GetAllZoomLevels()); |
| |
| const auto* extension_registry = extensions::ExtensionRegistry::Get(profile_); |
| |
| // Sort ZoomLevelChanges by host and scheme |
| // (a.com < http://a.com < https://a.com < b.com). |
| std::sort(zoom_levels.begin(), zoom_levels.end(), |
| [](const content::HostZoomMap::ZoomLevelChange& a, |
| const content::HostZoomMap::ZoomLevelChange& b) { |
| return a.host == b.host ? a.scheme < b.scheme : a.host < b.host; |
| }); |
| for (const auto& zoom_level : zoom_levels) { |
| base::Value::Dict exception; |
| switch (zoom_level.mode) { |
| case content::HostZoomMap::ZOOM_CHANGED_FOR_HOST: { |
| std::string host = zoom_level.host; |
| if (host == content::kUnreachableWebDataURL) { |
| host = |
| l10n_util::GetStringUTF8(IDS_ZOOMLEVELS_CHROME_ERROR_PAGES_LABEL); |
| } |
| exception.Set(site_settings::kOrigin, host); |
| |
| std::string display_name = host; |
| std::string origin_for_favicon = host; |
| // As an optimization, only check hosts that could be an extension. |
| if (crx_file::id_util::IdIsValid(host)) { |
| // Look up the host as an extension, if found then it is an extension. |
| const extensions::Extension* extension = |
| extension_registry->GetExtensionById( |
| host, extensions::ExtensionRegistry::EVERYTHING); |
| if (extension) { |
| origin_for_favicon = extension->url().spec(); |
| display_name = extension->name(); |
| } |
| } |
| exception.Set(site_settings::kDisplayName, display_name); |
| exception.Set(site_settings::kOriginForFavicon, origin_for_favicon); |
| break; |
| } |
| case content::HostZoomMap::ZOOM_CHANGED_FOR_SCHEME_AND_HOST: |
| // These are not stored in preferences and get cleared on next browser |
| // start. Therefore, we don't care for them. |
| continue; |
| case content::HostZoomMap::ZOOM_CHANGED_TEMPORARY_ZOOM: |
| NOTREACHED(); |
| } |
| |
| std::string setting_string = |
| content_settings::ContentSettingToString(CONTENT_SETTING_DEFAULT); |
| DCHECK(!setting_string.empty()); |
| |
| exception.Set(site_settings::kSetting, setting_string); |
| |
| // Calculate the zoom percent from the factor. Round up to the nearest whole |
| // number. |
| int zoom_percent = static_cast<int>( |
| blink::PageZoomLevelToZoomFactor(zoom_level.zoom_level) * 100 + 0.5); |
| exception.Set(kZoom, base::FormatPercent(zoom_percent)); |
| exception.Set(site_settings::kSource, |
| site_settings::SiteSettingSourceToString( |
| site_settings::SiteSettingSource::kPreference)); |
| // Append the new entry to the list and map. |
| zoom_levels_exceptions.Append(std::move(exception)); |
| } |
| |
| FireWebUIListener("onZoomLevelsChanged", zoom_levels_exceptions); |
| } |
| |
| void SiteSettingsHandler::HandleRemoveZoomLevel(const base::Value::List& args) { |
| CHECK_EQ(1U, args.size()); |
| |
| std::string origin = args[0].GetString(); |
| |
| if (origin == |
| l10n_util::GetStringUTF8(IDS_ZOOMLEVELS_CHROME_ERROR_PAGES_LABEL)) { |
| origin = content::kUnreachableWebDataURL; |
| } |
| |
| content::HostZoomMap* host_zoom_map; |
| host_zoom_map = content::HostZoomMap::GetDefaultForBrowserContext(profile_); |
| double default_level = host_zoom_map->GetDefaultZoomLevel(); |
| host_zoom_map->SetZoomLevelForHost(origin, default_level); |
| } |
| |
| void SiteSettingsHandler::HandleFetchBlockAutoplayStatus( |
| const base::Value::List& args) { |
| AllowJavascript(); |
| SendBlockAutoplayStatus(); |
| } |
| |
| void SiteSettingsHandler::SendBlockAutoplayStatus() { |
| if (!IsJavascriptAllowed()) |
| return; |
| |
| base::Value::Dict status; |
| |
| // Whether the block autoplay toggle should be checked. |
| base::Value::Dict pref; |
| pref.Set("value", |
| |
| UnifiedAutoplayConfig::ShouldBlockAutoplay(profile_) && |
| UnifiedAutoplayConfig::IsBlockAutoplayUserModifiable(profile_)); |
| status.Set("pref", std::move(pref)); |
| |
| // Whether the block autoplay toggle should be enabled. |
| status.Set("enabled", |
| UnifiedAutoplayConfig::IsBlockAutoplayUserModifiable(profile_)); |
| |
| FireWebUIListener("onBlockAutoplayStatusChanged", status); |
| } |
| |
| void SiteSettingsHandler::HandleSetBlockAutoplayEnabled( |
| const base::Value::List& args) { |
| AllowJavascript(); |
| |
| if (!UnifiedAutoplayConfig::IsBlockAutoplayUserModifiable(profile_)) |
| return; |
| |
| CHECK_EQ(1U, args.size()); |
| CHECK(args[0].is_bool()); |
| bool value = args[0].GetBool(); |
| |
| profile_->GetPrefs()->SetBoolean(prefs::kBlockAutoplayEnabled, value); |
| } |
| |
| void SiteSettingsHandler::RebuildModels() { |
| // The handler services two requests async once models have been built. |
| DCHECK(update_site_details_ || send_sites_list_); |
| |
| // Tests will directly fire the appropriate service method. |
| if (models_set_for_testing_) |
| return; |
| |
| // Don't do anything if the models are already in the process of being built. |
| // Requests will be serviced when the existing build process finishes. |
| if (num_models_being_built_ > 0) |
| return; |
| |
| // Reset any existing models. |
| // TODO(crbug.com/1368048) The implicit semantics of the handler require the |
| // models to be reset every time, but this is not required for all operations. |
| // A stronger call ordering enforcement, or stronger guarantees around when |
| // the models exist, could remove the requirement for this. |
| cookies_tree_model_.reset(); |
| browsing_data_model_.reset(); |
| |
| num_models_being_built_ = 2; |
| |
| BrowsingDataModel::BuildFromDisk( |
| profile_, ChromeBrowsingDataModelDelegate::CreateForProfile(profile_), |
| base::BindOnce(&SiteSettingsHandler::BrowsingDataModelCreated, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| cookies_tree_model_ = CookiesTreeModel::CreateForProfileDeprecated(profile_); |
| cookies_tree_model_->AddCookiesTreeObserver(this); |
| } |
| |
| void SiteSettingsHandler::ModelBuilt() { |
| DCHECK(num_models_being_built_ > 0); |
| num_models_being_built_--; |
| |
| if (num_models_being_built_ == 0) |
| ServicePendingRequests(); |
| } |
| |
| void SiteSettingsHandler::ServicePendingRequests() { |
| if (!IsJavascriptAllowed()) |
| return; |
| |
| if (send_sites_list_) |
| OnStorageFetched(); |
| if (update_site_details_) |
| OnGetUsageInfo(); |
| |
| send_sites_list_ = false; |
| update_site_details_ = false; |
| } |
| |
| void SiteSettingsHandler::ObserveSourcesForProfile(Profile* profile) { |
| auto* map = HostContentSettingsMapFactory::GetForProfile(profile); |
| if (!observations_.IsObservingSource(map)) |
| observations_.AddObservation(map); |
| |
| auto* usb_context = UsbChooserContextFactory::GetForProfile(profile); |
| if (!chooser_observations_.IsObservingSource(usb_context)) |
| chooser_observations_.AddObservation(usb_context); |
| |
| auto* serial_context = SerialChooserContextFactory::GetForProfile(profile); |
| if (!chooser_observations_.IsObservingSource(serial_context)) |
| chooser_observations_.AddObservation(serial_context); |
| |
| auto* hid_context = HidChooserContextFactory::GetForProfile(profile); |
| if (!chooser_observations_.IsObservingSource(hid_context)) |
| chooser_observations_.AddObservation(hid_context); |
| |
| if (base::FeatureList::IsEnabled( |
| features::kWebBluetoothNewPermissionsBackend)) { |
| auto* bluetooth_context = |
| BluetoothChooserContextFactory::GetForProfile(profile); |
| if (!chooser_observations_.IsObservingSource(bluetooth_context)) |
| chooser_observations_.AddObservation(bluetooth_context); |
| } |
| |
| observed_profiles_.AddObservation(profile); |
| } |
| |
| void SiteSettingsHandler::StopObservingSourcesForProfile(Profile* profile) { |
| auto* map = HostContentSettingsMapFactory::GetForProfile(profile); |
| if (observations_.IsObservingSource(map)) |
| observations_.RemoveObservation(map); |
| |
| auto* usb_context = UsbChooserContextFactory::GetForProfile(profile); |
| if (chooser_observations_.IsObservingSource(usb_context)) |
| chooser_observations_.RemoveObservation(usb_context); |
| |
| auto* serial_context = SerialChooserContextFactory::GetForProfile(profile); |
| if (chooser_observations_.IsObservingSource(serial_context)) |
| chooser_observations_.RemoveObservation(serial_context); |
| |
| auto* hid_context = HidChooserContextFactory::GetForProfile(profile); |
| if (chooser_observations_.IsObservingSource(hid_context)) |
| chooser_observations_.RemoveObservation(hid_context); |
| |
| if (base::FeatureList::IsEnabled( |
| features::kWebBluetoothNewPermissionsBackend)) { |
| auto* bluetooth_context = |
| BluetoothChooserContextFactory::GetForProfile(profile); |
| if (chooser_observations_.IsObservingSource(bluetooth_context)) |
| chooser_observations_.RemoveObservation(bluetooth_context); |
| } |
| |
| observed_profiles_.RemoveObservation(profile); |
| } |
| |
| void SiteSettingsHandler::TreeNodesAdded(ui::TreeModel* model, |
| ui::TreeModelNode* parent, |
| size_t start, |
| size_t count) {} |
| |
| void SiteSettingsHandler::TreeNodesRemoved(ui::TreeModel* model, |
| ui::TreeModelNode* parent, |
| size_t start, |
| size_t count) {} |
| |
| void SiteSettingsHandler::TreeNodeChanged(ui::TreeModel* model, |
| ui::TreeModelNode* node) {} |
| |
| void SiteSettingsHandler::TreeModelEndBatchDeprecated(CookiesTreeModel* model) { |
| ModelBuilt(); |
| } |
| |
| void SiteSettingsHandler::GetOriginStorage( |
| AllSitesMap* all_sites_map, |
| std::map<url::Origin, int64_t>* origin_size_map) { |
| CHECK(cookies_tree_model_.get()); |
| |
| for (const auto& site : cookies_tree_model_->GetRoot()->children()) { |
| int64_t size = site->InclusiveSize(); |
| if (size == 0) |
| continue; |
| UpdateDataFromModel(all_sites_map, origin_size_map, |
| site->GetDetailedInfo().origin, size); |
| } |
| |
| for (const auto& entry : *browsing_data_model_) { |
| if (entry.data_details->storage_size == 0) |
| continue; |
| |
| // Convert the primary host to an HTTPS url to match expecations for this |
| // code. |
| url::Origin origin = |
| ConvertEtldToOrigin(*entry.primary_host, /*secure=*/true); |
| UpdateDataFromModel(all_sites_map, origin_size_map, origin, |
| entry.data_details->storage_size); |
| } |
| } |
| |
| void SiteSettingsHandler::GetHostCookies( |
| AllSitesMap* all_sites_map, |
| std::map<std::pair<std::string, absl::optional<std::string>>, int>* |
| host_cookie_map) { |
| CHECK(cookies_tree_model_.get()); |
| // Get sites that don't have data but have cookies. |
| // TODO(crbug.com/1271155): Query the Browsing Data Model instead when cookie |
| // information is available there. |
| for (const auto& site : cookies_tree_model_->GetRoot()->children()) { |
| const url::Origin& origin = site->GetDetailedInfo().origin; |
| if (!site->NumberOfCookies()) |
| continue; |
| |
| // Each cookie will need to be inspected to see if it is partitioned, so it |
| // may be associated with the appropriate eTLD+1. |
| // TODO (crbug.com/1271155): This is slow, the replacement for the |
| // CookiesTreeModel should improve this significantly. |
| for (const auto& site_child : site->children()) { |
| if (site_child->GetDetailedInfo().node_type != |
| CookieTreeNode::DetailedInfo::TYPE_COOKIES) { |
| continue; |
| } |
| |
| for (const auto& cookie : site_child->children()) { |
| const auto& detailed_info = cookie->GetDetailedInfo(); |
| DCHECK(detailed_info.node_type == |
| CookieTreeNode::DetailedInfo::TYPE_COOKIE); |
| DCHECK(detailed_info.cookie); |
| |
| absl::optional<std::string> partition_etld_plus1 = absl::nullopt; |
| absl::optional<GroupingKey> partition_grouping_key = absl::nullopt; |
| if (detailed_info.cookie->IsPartitioned()) { |
| partition_etld_plus1 = |
| detailed_info.cookie->PartitionKey()->site().GetURL().host(); |
| partition_grouping_key = |
| GroupingKey::CreateFromEtldPlus1(*partition_etld_plus1); |
| } |
| InsertOriginIntoGroup(all_sites_map, origin, |
| /*is_origin_with_cookies=*/true, |
| partition_grouping_key); |
| (*host_cookie_map)[{origin.host(), partition_etld_plus1}]++; |
| } |
| } |
| } |
| } |
| |
| void SiteSettingsHandler::HandleClearEtldPlus1DataAndCookies( |
| const base::Value::List& args) { |
| CHECK_EQ(1U, args.size()); |
| const std::string& etld_plus1 = args[0].GetString(); |
| auto grouping_key = GroupingKey::CreateFromEtldPlus1(etld_plus1); |
| |
| AllowJavascript(); |
| RemoveMatchingNodes(cookies_tree_model_.get(), absl::nullopt, etld_plus1); |
| |
| // Retrieve all of the origin entries grouped under this eTLD + 1. |
| std::vector<url::Origin> affected_origins; |
| for (const auto& origin_is_partitioned : all_sites_map_[grouping_key]) { |
| // Ignore entries which are partitioned, as no non-cookie tree storage is |
| // partitioned. |
| if (origin_is_partitioned.second) |
| continue; |
| |
| affected_origins.emplace_back( |
| // A placeholder origin may have been created, in this case the |
| // grouping key itself should be used as the origin, the same as it |
| // would have been for display. |
| ResolveOriginInSiteGroup(grouping_key, origin_is_partitioned.first)); |
| } |
| |
| // Cookies may have associated with the entry for the grouping url itself. |
| // As per the logic in InsertOriginIntoGroup, this will only occur |
| // if the existing entry was https, otherwise a new http entry would be |
| // created for the placeholder. Hence, we need only additionally include the |
| // HTTPS version of the eTLD+1 as an origin. |
| affected_origins.emplace_back( |
| ConvertEtldToOrigin(etld_plus1, /*secure=*/true)); |
| |
| RemoveNonTreeModelData(affected_origins); |
| } |
| |
| void SiteSettingsHandler::HandleRecordAction(const base::Value::List& args) { |
| const auto& list = args; |
| CHECK_EQ(1U, list.size()); |
| int action = list[0].GetInt(); |
| DCHECK_LE(action, static_cast<int>(AllSitesAction2::kMaxValue)); |
| DCHECK_GE(action, static_cast<int>(AllSitesAction2::kLoadPage)); |
| |
| LogAllSitesAction(static_cast<AllSitesAction2>(action)); |
| } |
| |
| void SiteSettingsHandler::HandleGetNumCookiesString( |
| const base::Value::List& args) { |
| CHECK_EQ(2U, args.size()); |
| std::string callback_id; |
| callback_id = args[0].GetString(); |
| int num_cookies = args[1].GetInt(); |
| |
| AllowJavascript(); |
| const std::u16string string = |
| num_cookies > 0 ? l10n_util::GetPluralStringFUTF16( |
| IDS_SETTINGS_SITE_SETTINGS_NUM_COOKIES, num_cookies) |
| : std::u16string(); |
| |
| ResolveJavascriptCallback(base::Value(callback_id), base::Value(string)); |
| } |
| |
| void SiteSettingsHandler::RemoveNonTreeModelData( |
| const std::vector<url::Origin>& origins) { |
| // TODO(crbug.com/1268626): Remove client hint information, which cannot be |
| // associated with Cookie node information as the scheme in the cookie node |
| // may not match due to HTTP / HTTPS distinction issues. |
| for (const auto& origin : origins) { |
| HostContentSettingsMapFactory::GetForProfile(profile_) |
| ->SetWebsiteSettingDefaultScope(origin.GetURL(), GURL(), |
| ContentSettingsType::CLIENT_HINTS, |
| base::Value()); |
| // Once user clears site setting data for `origins`, all corresponding |
| // reduced accept language stored in the setting map should also be cleaned. |
| HostContentSettingsMapFactory::GetForProfile(profile_) |
| ->SetWebsiteSettingDefaultScope( |
| origin.GetURL(), GURL(), |
| ContentSettingsType::REDUCED_ACCEPT_LANGUAGE, base::Value()); |
| } |
| // Remove Privacy Sandbox API data. |
| content::BrowsingDataRemover* remover = profile_->GetBrowsingDataRemover(); |
| std::unique_ptr<content::BrowsingDataFilterBuilder> filter = |
| content::BrowsingDataFilterBuilder::Create( |
| content::BrowsingDataFilterBuilder::Mode::kDelete); |
| for (const auto& origin : origins) { |
| filter->AddOrigin(origin); |
| } |
| remover->RemoveWithFilter( |
| base::Time::Min(), base::Time::Max(), |
| content::BrowsingDataRemover::DATA_TYPE_PRIVACY_SANDBOX & |
| // Part of BrowsingDataModel: |
| ~content::BrowsingDataRemover::DATA_TYPE_TRUST_TOKENS, |
| content::BrowsingDataRemover::ORIGIN_TYPE_UNPROTECTED_WEB, |
| std::move(filter)); |
| |
| // Remove Privacy Sandbox API data not integrated with the |
| // BrowsingDataRemover. |
| if (auto* browsing_topics_service = |
| browsing_topics::BrowsingTopicsServiceFactory::GetForProfile( |
| profile_)) { |
| for (const auto& origin : origins) { |
| browsing_topics_service->ClearTopicsDataForOrigin(origin); |
| } |
| } |
| |
| // Remove any Browsing Data Model data associated with the origins host. |
| // TODO(crbug.com/1271155) - When the browsing data model supports all storage |
| // types, re-work this handler to work directly with primary hosts as defined |
| // by the model. |
| // TODO(crbug.com/1368048) - Permission info loading before storage info |
| // can result in an interleaving of actions that means this pointer is |
| // null (as it hasn't loaded yet, but the user can delete an entry which has |
| // been created by permission info). |
| if (browsing_data_model_) { |
| for (const auto& origin : origins) { |
| browsing_data_model_->RemoveBrowsingData(origin.host(), |
| base::DoNothing()); |
| } |
| } |
| |
| #if BUILDFLAG(IS_WIN) |
| // Removes any Media License Data associated with the origin that is not |
| // stored in quota nodes. This should only be on Windows as ChromeOS does |
| // not support removing Media License Data per origin, and |
| // site_settings_handler.cc does not handle Android site specific code. |
| // The code for Android site specific code is located in |
| // components/browser_ui/site_settings/android/website_preference_bridge.cc |
| // TODO(b/248311157) - When CrOS supports the ability to delete platform |
| // keys by domain, implement the CrOS specific logic regarding clearing site |
| // specific media license data. |
| // TODO(b/248311157) - When the migration to BrowsingDataModel is finished, |
| // remove this and integrate the media license data removal steps there. |
| auto filter_builder = content::BrowsingDataFilterBuilder::Create( |
| content::BrowsingDataFilterBuilder::Mode::kDelete); |
| |
| for (const auto& origin : origins) |
| filter_builder->AddOrigin(origin); |
| |
| CdmDocumentServiceImpl::ClearCdmData( |
| profile_, base::Time::Min(), base::Time::Max(), |
| filter_builder->BuildUrlFilter(), base::DoNothing()); |
| #endif // BUILDFLAG(IS_WIN) |
| } |
| |
| void SiteSettingsHandler::SetModelsForTesting( |
| std::unique_ptr<CookiesTreeModel> cookies_tree_model, |
| std::unique_ptr<BrowsingDataModel> browsing_data_model) { |
| request_started_time_ = base::TimeTicks::Now(); |
| cookies_tree_model_ = std::move(cookies_tree_model); |
| browsing_data_model_ = std::move(browsing_data_model); |
| models_set_for_testing_ = true; |
| } |
| |
| void SiteSettingsHandler::ClearAllSitesMapForTesting() { |
| all_sites_map_.clear(); |
| } |
| |
| void SiteSettingsHandler::SendCookieSettingDescription() { |
| FireWebUIListener("cookieSettingDescriptionChanged", |
| base::Value(GetCookieSettingDescription(profile_))); |
| } |
| |
| base::Value::List |
| SiteSettingsHandler::PopulateNotificationPermissionReviewData() { |
| base::Value::List result; |
| if (!base::FeatureList::IsEnabled( |
| features::kSafetyCheckNotificationPermissions)) |
| return result; |
| |
| auto* service = |
| NotificationPermissionsReviewServiceFactory::GetForProfile(profile_); |
| if (!service) |
| return result; |
| |
| auto notification_permissions = service->GetNotificationSiteListForReview(); |
| |
| site_engagement::SiteEngagementService* engagement_service = |
| site_engagement::SiteEngagementService::Get(profile_); |
| |
| // Sort notification permissions by their priority for surfacing to the user. |
| auto notification_permission_ordering = |
| [](const permissions::NotificationPermissions& left, |
| const permissions::NotificationPermissions& right) { |
| return left.notification_count > right.notification_count; |
| }; |
| std::sort(notification_permissions.begin(), notification_permissions.end(), |
| notification_permission_ordering); |
| |
| for (const auto& notification_permission : notification_permissions) { |
| // Converting primary pattern to GURL should always be valid, since |
| // Notification Permission Review list only contains single origins. Those |
| // are filtered in |
| // NotificationPermissionsReviewService::GetNotificationSiteListForReview. |
| GURL url = GURL(notification_permission.primary_pattern.ToString()); |
| DCHECK(url.is_valid()); |
| if (!ShouldAddToNotificationPermissionReviewList( |
| engagement_service, url, |
| notification_permission.notification_count)) { |
| continue; |
| } |
| |
| base::Value::Dict permission; |
| permission.Set(site_settings::kOrigin, |
| notification_permission.primary_pattern.ToString()); |
| |
| std::string notification_info_string = |
| base::UTF16ToUTF8(l10n_util::GetPluralStringFUTF16( |
| IDS_SETTINGS_SAFETY_CHECK_REVIEW_NOTIFICATION_PERMISSIONS_COUNT_LABEL, |
| notification_permission.notification_count)); |
| permission.Set(site_settings::kNotificationInfoString, |
| notification_info_string); |
| result.Append(std::move(permission)); |
| } |
| |
| return result; |
| } |
| |
| // Dictionary keys for an individual `FileSystemPermissionGrant`. |
| // Schema (per grant): |
| // { |
| // "origin" : <string>; |
| // "filePath" : <string>; |
| // "isWritable" : <bool>; |
| // "isDirectory" : <bool>; |
| // } |
| |
| // Dictionary keys for an individual permission grant in |
| // the returned `grants` List. |
| // Note that while the `isWritable` and `isDirectory` values |
| // are implied by the names of the grant lists, the |
| // `FileSystemPermissionGrant` type contains these attributes |
| // in order to make the data more easily accessible from the UI code. |
| // |
| // Schema (per origin): |
| // [ |
| // ... |
| // { |
| // "origin" : <string>; |
| // "directoryReadGrants" : FileSystemPermissionGrant[]; |
| // "directoryWriteGrants" : FileSystemPermissionGrant[]; |
| // "fileReadGrants" : FileSystemPermissionGrant[]; |
| // "fileWriteGrants" : FileSystemPermissionGrant[]; |
| // } |
| // ... |
| // ] |
| base::Value::List SiteSettingsHandler::PopulateFileSystemGrantData() { |
| base::Value::List grants; |
| |
| // TODO(crbug.com/1373962): Remove feature flag check after persisted |
| // permissions is fully launched. |
| if (!base::FeatureList::IsEnabled( |
| features::kFileSystemAccessPersistentPermissions)) |
| return grants; |
| ChromeFileSystemAccessPermissionContext* permission_context = |
| FileSystemAccessPermissionContextFactory::GetForProfile(profile_); |
| std::vector<url::Origin> origins_with_grants = |
| permission_context->GetOriginsWithGrants(); |
| |
| for (auto& origin : origins_with_grants) { |
| ChromeFileSystemAccessPermissionContext::Grants grantObj = |
| permission_context->GetPermissionGrants(origin); |
| if (grantObj.file_read_grants.empty() && |
| grantObj.file_write_grants.empty() && |
| grantObj.directory_read_grants.empty() && |
| grantObj.directory_write_grants.empty()) { |
| continue; |
| } |
| |
| base::Value::Dict file_system_permission_grant; |
| base::Value::List directory_read_grants; |
| base::Value::List directory_write_grants; |
| base::Value::List file_read_grants; |
| base::Value::List file_write_grants; |
| std::string origin_string = origin.GetURL().spec(); |
| file_system_permission_grant.Set(site_settings::kOrigin, origin_string); |
| |
| // Populate the `file_system_permission_grant` object with allowed |
| // permissions. |
| for (auto& file_path : grantObj.directory_write_grants) { |
| base::Value::Dict directory_write_grant; |
| directory_write_grant.Set(site_settings::kOrigin, origin_string); |
| directory_write_grant.Set(site_settings::kFilePath, |
| FilePathToValue(file_path)); |
| directory_write_grant.Set(site_settings::kIsWritable, true); |
| directory_write_grant.Set(site_settings::kIsDirectory, true); |
| directory_write_grants.Append(std::move(directory_write_grant)); |
| } |
| file_system_permission_grant.Set(site_settings::kDirectoryWriteGrants, |
| std::move(directory_write_grants)); |
| |
| for (auto& file_path : grantObj.directory_read_grants) { |
| base::Value::Dict directory_read_grant; |
| directory_read_grant.Set(site_settings::kOrigin, origin_string); |
| directory_read_grant.Set(site_settings::kFilePath, |
| FilePathToValue(file_path)); |
| directory_read_grant.Set(site_settings::kIsWritable, false); |
| directory_read_grant.Set(site_settings::kIsDirectory, true); |
| directory_read_grants.Append(std::move(directory_read_grant)); |
| } |
| file_system_permission_grant.Set(site_settings::kDirectoryReadGrants, |
| std::move(directory_read_grants)); |
| |
| for (auto& file_path : grantObj.file_write_grants) { |
| base::Value::Dict file_write_grant; |
| file_write_grant.Set(site_settings::kOrigin, origin_string); |
| file_write_grant.Set(site_settings::kFilePath, |
| FilePathToValue(file_path)); |
| file_write_grant.Set(site_settings::kIsWritable, true); |
| file_write_grant.Set(site_settings::kIsDirectory, false); |
| file_write_grants.Append(std::move(file_write_grant)); |
| } |
| file_system_permission_grant.Set(site_settings::kFileWriteGrants, |
| std::move(file_write_grants)); |
| |
| for (auto& file_path : grantObj.file_read_grants) { |
| base::Value::Dict file_read_grant; |
| file_read_grant.Set(site_settings::kOrigin, origin_string); |
| file_read_grant.Set(site_settings::kFilePath, FilePathToValue(file_path)); |
| file_read_grant.Set(site_settings::kIsWritable, false); |
| file_read_grant.Set(site_settings::kIsDirectory, false); |
| file_read_grants.Append((base::Value(std::move(file_read_grant)))); |
| } |
| file_system_permission_grant.Set(site_settings::kFileReadGrants, |
| std::move(file_read_grants)); |
| grants.Append(std::move(file_system_permission_grant)); |
| } |
| return grants; |
| } |
| |
| void SiteSettingsHandler::SendNotificationPermissionReviewList() { |
| if (!base::FeatureList::IsEnabled( |
| features::kSafetyCheckNotificationPermissions)) { |
| return; |
| } |
| // Notify observers that the permission review list could have changed. Note |
| // that the list is not guaranteed to have changed. In places where |
| // determining whether the list has changed is cause for performance concerns, |
| // an unchanged list may be sent. This is the case for |
| // HandleResetCategoryPermissionForPattern and |
| // HandleSetCategoryPermissionForPattern. |
| FireWebUIListener("notification-permission-review-list-maybe-changed", |
| PopulateNotificationPermissionReviewData()); |
| } |
| |
| } // namespace settings |