| OLD | NEW |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/engagement/important_sites_util.h" | 5 #include "chrome/browser/engagement/important_sites_util.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 #include <map> | 8 #include <map> |
| 9 #include <memory> | 9 #include <memory> |
| 10 #include <set> | 10 #include <set> |
| 11 #include <utility> | 11 #include <utility> |
| 12 | 12 |
| 13 #include "base/containers/hash_tables.h" | 13 #include "base/containers/hash_tables.h" |
| 14 #include "base/memory/ptr_util.h" | 14 #include "base/memory/ptr_util.h" |
| 15 #include "base/metrics/histogram_macros.h" | 15 #include "base/metrics/histogram_macros.h" |
| 16 #include "base/stl_util.h" | 16 #include "base/stl_util.h" |
| 17 #include "base/time/time.h" | 17 #include "base/time/time.h" |
| 18 #include "base/values.h" | 18 #include "base/values.h" |
| 19 #include "chrome/browser/banners/app_banner_settings_helper.h" | 19 #include "chrome/browser/banners/app_banner_settings_helper.h" |
| 20 #include "chrome/browser/bookmarks/bookmark_model_factory.h" | 20 #include "chrome/browser/bookmarks/bookmark_model_factory.h" |
| 21 #include "chrome/browser/content_settings/host_content_settings_map_factory.h" | 21 #include "chrome/browser/content_settings/host_content_settings_map_factory.h" |
| 22 #include "chrome/browser/engagement/site_engagement_score.h" | 22 #include "chrome/browser/engagement/site_engagement_score.h" |
| 23 #include "chrome/browser/engagement/site_engagement_service.h" | 23 #include "chrome/browser/engagement/site_engagement_service.h" |
| 24 #include "chrome/browser/profiles/profile.h" | 24 #include "chrome/browser/profiles/profile.h" |
| 25 #include "chrome/common/pref_names.h" |
| 25 #include "components/bookmarks/browser/bookmark_model.h" | 26 #include "components/bookmarks/browser/bookmark_model.h" |
| 26 #include "components/content_settings/core/browser/host_content_settings_map.h" | 27 #include "components/content_settings/core/browser/host_content_settings_map.h" |
| 27 #include "components/content_settings/core/common/content_settings.h" | 28 #include "components/content_settings/core/common/content_settings.h" |
| 29 #include "components/pref_registry/pref_registry_syncable.h" |
| 30 #include "components/prefs/pref_service.h" |
| 31 #include "components/prefs/scoped_user_pref_update.h" |
| 28 #include "net/base/registry_controlled_domains/registry_controlled_domain.h" | 32 #include "net/base/registry_controlled_domains/registry_controlled_domain.h" |
| 29 #include "third_party/WebKit/public/platform/site_engagement.mojom.h" | 33 #include "third_party/WebKit/public/platform/site_engagement.mojom.h" |
| 30 #include "url/gurl.h" | 34 #include "url/gurl.h" |
| 31 | 35 |
| 32 namespace { | 36 namespace { |
| 33 using bookmarks::BookmarkModel; | 37 using bookmarks::BookmarkModel; |
| 34 using ImportantDomainInfo = ImportantSitesUtil::ImportantDomainInfo; | 38 using ImportantDomainInfo = ImportantSitesUtil::ImportantDomainInfo; |
| 35 | 39 |
| 40 // Note: These values are stored on both the per-site content settings |
| 41 // dictionary and the dialog preference dictionary. |
| 42 |
| 43 static const char kTimeLastIgnored[] = "TimeLastIgnored"; |
| 44 static const int kBlacklistExpirationTimeDays = 30 * 5; |
| 45 |
| 36 static const char kNumTimesIgnoredName[] = "NumTimesIgnored"; | 46 static const char kNumTimesIgnoredName[] = "NumTimesIgnored"; |
| 37 static const int kTimesIgnoredForBlacklist = 3; | 47 static const int kTimesIgnoredForBlacklist = 3; |
| 38 | 48 |
| 39 // These are the maximum # of bookmarks we can use as signals. If the user has | 49 // These are the maximum # of bookmarks we can use as signals. If the user has |
| 40 // <= kMaxBookmarks, then we just use those bookmarks. Otherwise we filter all | 50 // <= kMaxBookmarks, then we just use those bookmarks. Otherwise we filter all |
| 41 // bookmarks on site engagement > 0, sort, and trim to kMaxBookmarks. | 51 // bookmarks on site engagement > 0, sort, and trim to kMaxBookmarks. |
| 42 static const int kMaxBookmarks = 5; | 52 static const int kMaxBookmarks = 5; |
| 43 | 53 |
| 44 // Do not change the values here, as they are used for UMA histograms. | 54 // Do not change the values here, as they are used for UMA histograms. |
| 45 enum ImportantReason { | 55 enum ImportantReason { |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 77 CROSSED_NOTIFICATIONS = 1, | 87 CROSSED_NOTIFICATIONS = 1, |
| 78 CROSSED_ENGAGEMENT = 2, | 88 CROSSED_ENGAGEMENT = 2, |
| 79 CROSSED_NOTIFICATIONS_AND_ENGAGEMENT = 3, | 89 CROSSED_NOTIFICATIONS_AND_ENGAGEMENT = 3, |
| 80 CROSSED_DURABLE_AND_ENGAGEMENT = 4, | 90 CROSSED_DURABLE_AND_ENGAGEMENT = 4, |
| 81 CROSSED_NOTIFICATIONS_AND_DURABLE = 5, | 91 CROSSED_NOTIFICATIONS_AND_DURABLE = 5, |
| 82 CROSSED_NOTIFICATIONS_AND_DURABLE_AND_ENGAGEMENT = 6, | 92 CROSSED_NOTIFICATIONS_AND_DURABLE_AND_ENGAGEMENT = 6, |
| 83 CROSSED_REASON_UNKNOWN = 7, | 93 CROSSED_REASON_UNKNOWN = 7, |
| 84 CROSSED_REASON_BOUNDARY | 94 CROSSED_REASON_BOUNDARY |
| 85 }; | 95 }; |
| 86 | 96 |
| 97 void RecordIgnore(base::DictionaryValue* dict) { |
| 98 int times_ignored = 0; |
| 99 dict->GetInteger(kNumTimesIgnoredName, ×_ignored); |
| 100 dict->SetInteger(kNumTimesIgnoredName, ++times_ignored); |
| 101 dict->SetDouble(kTimeLastIgnored, base::Time::Now().ToDoubleT()); |
| 102 } |
| 103 |
| 104 // If we should blacklist the item with the given dictionary ignored record. |
| 105 bool ShouldSuppressItem(base::DictionaryValue* dict) { |
| 106 double last_ignored_time = 0; |
| 107 if (dict->GetDouble(kTimeLastIgnored, &last_ignored_time)) { |
| 108 base::TimeDelta diff = |
| 109 base::Time::Now() - base::Time::FromDoubleT(last_ignored_time); |
| 110 if (diff >= base::TimeDelta::FromDays(kBlacklistExpirationTimeDays)) { |
| 111 dict->SetInteger(kNumTimesIgnoredName, 0); |
| 112 dict->Remove(kTimeLastIgnored, nullptr); |
| 113 return false; |
| 114 } |
| 115 } |
| 116 |
| 117 int times_ignored = 0; |
| 118 return dict->GetInteger(kNumTimesIgnoredName, ×_ignored) && |
| 119 times_ignored >= kTimesIgnoredForBlacklist; |
| 120 } |
| 121 |
| 87 CrossedReason GetCrossedReasonFromBitfield(int32_t reason_bitfield) { | 122 CrossedReason GetCrossedReasonFromBitfield(int32_t reason_bitfield) { |
| 88 bool durable = (reason_bitfield & (1 << ImportantReason::DURABLE)) != 0; | 123 bool durable = (reason_bitfield & (1 << ImportantReason::DURABLE)) != 0; |
| 89 bool notifications = | 124 bool notifications = |
| 90 (reason_bitfield & (1 << ImportantReason::NOTIFICATIONS)) != 0; | 125 (reason_bitfield & (1 << ImportantReason::NOTIFICATIONS)) != 0; |
| 91 bool engagement = (reason_bitfield & (1 << ImportantReason::ENGAGEMENT)) != 0; | 126 bool engagement = (reason_bitfield & (1 << ImportantReason::ENGAGEMENT)) != 0; |
| 92 if (durable && notifications && engagement) | 127 if (durable && notifications && engagement) |
| 93 return CROSSED_NOTIFICATIONS_AND_DURABLE_AND_ENGAGEMENT; | 128 return CROSSED_NOTIFICATIONS_AND_DURABLE_AND_ENGAGEMENT; |
| 94 else if (notifications && durable) | 129 else if (notifications && durable) |
| 95 return CROSSED_NOTIFICATIONS_AND_DURABLE; | 130 return CROSSED_NOTIFICATIONS_AND_DURABLE; |
| 96 else if (notifications && engagement) | 131 else if (notifications && engagement) |
| (...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 191 } | 226 } |
| 192 | 227 |
| 193 std::unique_ptr<base::DictionaryValue> dict = | 228 std::unique_ptr<base::DictionaryValue> dict = |
| 194 base::DictionaryValue::From(map->GetWebsiteSetting( | 229 base::DictionaryValue::From(map->GetWebsiteSetting( |
| 195 origin, origin, CONTENT_SETTINGS_TYPE_IMPORTANT_SITE_INFO, "", | 230 origin, origin, CONTENT_SETTINGS_TYPE_IMPORTANT_SITE_INFO, "", |
| 196 nullptr)); | 231 nullptr)); |
| 197 | 232 |
| 198 if (!dict) | 233 if (!dict) |
| 199 continue; | 234 continue; |
| 200 | 235 |
| 201 int times_ignored = 0; | 236 if (ShouldSuppressItem(dict.get())) |
| 202 if (!dict->GetInteger(kNumTimesIgnoredName, ×_ignored) || | 237 ignoring_domains.insert(origin.host()); |
| 203 times_ignored < kTimesIgnoredForBlacklist) { | |
| 204 continue; | |
| 205 } | |
| 206 | |
| 207 ignoring_domains.insert(origin.host()); | |
| 208 } | 238 } |
| 209 return ignoring_domains; | 239 return ignoring_domains; |
| 210 } | 240 } |
| 211 | 241 |
| 212 void PopulateInfoMapWithSiteEngagement( | 242 void PopulateInfoMapWithSiteEngagement( |
| 213 Profile* profile, | 243 Profile* profile, |
| 214 blink::mojom::EngagementLevel minimum_engagement, | 244 blink::mojom::EngagementLevel minimum_engagement, |
| 215 std::map<GURL, double>* engagement_map, | 245 std::map<GURL, double>* engagement_map, |
| 216 base::hash_map<std::string, ImportantDomainInfo>* output) { | 246 base::hash_map<std::string, ImportantDomainInfo>* output) { |
| 217 SiteEngagementService* service = SiteEngagementService::Get(profile); | 247 SiteEngagementService* service = SiteEngagementService::Get(profile); |
| (...skipping 95 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 313 GURL origin(site.primary_pattern.ToString()); | 343 GURL origin(site.primary_pattern.ToString()); |
| 314 if (!AppBannerSettingsHelper::WasLaunchedRecently(profile, origin, now)) | 344 if (!AppBannerSettingsHelper::WasLaunchedRecently(profile, origin, now)) |
| 315 continue; | 345 continue; |
| 316 MaybePopulateImportantInfoForReason(origin, &content_origins, | 346 MaybePopulateImportantInfoForReason(origin, &content_origins, |
| 317 ImportantReason::HOME_SCREEN, output); | 347 ImportantReason::HOME_SCREEN, output); |
| 318 } | 348 } |
| 319 } | 349 } |
| 320 | 350 |
| 321 } // namespace | 351 } // namespace |
| 322 | 352 |
| 353 bool ImportantSitesUtil::IsDialogDisabled(Profile* profile) { |
| 354 PrefService* service = profile->GetPrefs(); |
| 355 DictionaryPrefUpdate update(service, prefs::kImportantSitesDialogHistory); |
| 356 |
| 357 return ShouldSuppressItem(update.Get()); |
| 358 } |
| 359 |
| 360 void ImportantSitesUtil::RegisterProfilePrefs( |
| 361 user_prefs::PrefRegistrySyncable* registry) { |
| 362 registry->RegisterDictionaryPref(prefs::kImportantSitesDialogHistory); |
| 363 } |
| 364 |
| 323 std::vector<ImportantDomainInfo> | 365 std::vector<ImportantDomainInfo> |
| 324 ImportantSitesUtil::GetImportantRegisterableDomains(Profile* profile, | 366 ImportantSitesUtil::GetImportantRegisterableDomains(Profile* profile, |
| 325 size_t max_results) { | 367 size_t max_results) { |
| 326 base::hash_map<std::string, ImportantDomainInfo> important_info; | 368 base::hash_map<std::string, ImportantDomainInfo> important_info; |
| 327 std::map<GURL, double> engagement_map; | 369 std::map<GURL, double> engagement_map; |
| 328 | 370 |
| 329 PopulateInfoMapWithSiteEngagement( | 371 PopulateInfoMapWithSiteEngagement( |
| 330 profile, blink::mojom::EngagementLevel::MEDIUM, &engagement_map, | 372 profile, blink::mojom::EngagementLevel::MEDIUM, &engagement_map, |
| 331 &important_info); | 373 &important_info); |
| 332 | 374 |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 378 RECORD_UMA_FOR_IMPORTANT_REASON( | 420 RECORD_UMA_FOR_IMPORTANT_REASON( |
| 379 "Storage.ImportantSites.CBDChosenReason", | 421 "Storage.ImportantSites.CBDChosenReason", |
| 380 "Storage.ImportantSites.CBDChosenReasonCount", reason_bitfield); | 422 "Storage.ImportantSites.CBDChosenReasonCount", reason_bitfield); |
| 381 } | 423 } |
| 382 for (int32_t reason_bitfield : ignored_sites_reason_bitfield) { | 424 for (int32_t reason_bitfield : ignored_sites_reason_bitfield) { |
| 383 RECORD_UMA_FOR_IMPORTANT_REASON( | 425 RECORD_UMA_FOR_IMPORTANT_REASON( |
| 384 "Storage.ImportantSites.CBDIgnoredReason", | 426 "Storage.ImportantSites.CBDIgnoredReason", |
| 385 "Storage.ImportantSites.CBDIgnoredReasonCount", reason_bitfield); | 427 "Storage.ImportantSites.CBDIgnoredReasonCount", reason_bitfield); |
| 386 } | 428 } |
| 387 | 429 |
| 388 // We use the ignored sites to update our important sites blacklist. | |
| 389 HostContentSettingsMap* map = | 430 HostContentSettingsMap* map = |
| 390 HostContentSettingsMapFactory::GetForProfile(profile); | 431 HostContentSettingsMapFactory::GetForProfile(profile); |
| 391 for (const std::string& ignored_site : ignored_sites) { | |
| 392 GURL origin("http://" + ignored_site); | |
| 393 std::unique_ptr<base::Value> value = map->GetWebsiteSetting( | |
| 394 origin, origin, CONTENT_SETTINGS_TYPE_IMPORTANT_SITE_INFO, "", nullptr); | |
| 395 | 432 |
| 396 std::unique_ptr<base::DictionaryValue> dict = | 433 // We use the ignored sites to update our important sites blacklist only if |
| 397 base::DictionaryValue::From(map->GetWebsiteSetting( | 434 // the user chose to blacklist a site. |
| 398 origin, origin, CONTENT_SETTINGS_TYPE_IMPORTANT_SITE_INFO, "", | 435 if (!blacklisted_sites.empty()) { |
| 399 nullptr)); | 436 for (const std::string& ignored_site : ignored_sites) { |
| 437 GURL origin("http://" + ignored_site); |
| 438 std::unique_ptr<base::DictionaryValue> dict = |
| 439 base::DictionaryValue::From(map->GetWebsiteSetting( |
| 440 origin, origin, CONTENT_SETTINGS_TYPE_IMPORTANT_SITE_INFO, "", |
| 441 nullptr)); |
| 400 | 442 |
| 401 int times_ignored = 0; | 443 if (!dict) |
| 402 if (dict) | 444 dict = base::MakeUnique<base::DictionaryValue>(); |
| 403 dict->GetInteger(kNumTimesIgnoredName, ×_ignored); | |
| 404 else | |
| 405 dict = base::MakeUnique<base::DictionaryValue>(); | |
| 406 dict->SetInteger(kNumTimesIgnoredName, ++times_ignored); | |
| 407 | 445 |
| 446 RecordIgnore(dict.get()); |
| 447 |
| 448 map->SetWebsiteSettingDefaultScope( |
| 449 origin, origin, CONTENT_SETTINGS_TYPE_IMPORTANT_SITE_INFO, "", |
| 450 std::move(dict)); |
| 451 } |
| 452 } else { |
| 453 // Record that the user did not interact with the dialog. |
| 454 PrefService* service = profile->GetPrefs(); |
| 455 DictionaryPrefUpdate update(service, prefs::kImportantSitesDialogHistory); |
| 456 RecordIgnore(update.Get()); |
| 457 } |
| 458 |
| 459 // We clear our blacklist for sites that the user chose. |
| 460 for (const std::string& blacklisted_site : blacklisted_sites) { |
| 461 GURL origin("http://" + blacklisted_site); |
| 462 std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); |
| 463 dict->SetInteger(kNumTimesIgnoredName, 0); |
| 464 dict->Remove(kTimeLastIgnored, nullptr); |
| 408 map->SetWebsiteSettingDefaultScope( | 465 map->SetWebsiteSettingDefaultScope( |
| 409 origin, origin, CONTENT_SETTINGS_TYPE_IMPORTANT_SITE_INFO, "", | 466 origin, origin, CONTENT_SETTINGS_TYPE_IMPORTANT_SITE_INFO, "", |
| 410 std::move(dict)); | 467 std::move(dict)); |
| 411 } | |
| 412 | |
| 413 // We clear our blacklist for sites that the user chose. | |
| 414 for (const std::string& ignored_site : blacklisted_sites) { | |
| 415 GURL origin("http://" + ignored_site); | |
| 416 std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); | |
| 417 dict->SetInteger(kNumTimesIgnoredName, 0); | |
| 418 map->SetWebsiteSettingDefaultScope( | |
| 419 origin, origin, CONTENT_SETTINGS_TYPE_IMPORTANT_SITE_INFO, "", | |
| 420 std::move(dict)); | |
| 421 } | 468 } |
| 422 | 469 |
| 423 // Finally, record our old crossed-stats. | 470 // Finally, record our old crossed-stats. |
| 424 // Note: we don't plan on adding new metrics here, this is just for the finch | 471 // Note: we don't plan on adding new metrics here, this is just for the finch |
| 425 // experiment to give us initial data on what signals actually mattered. | 472 // experiment to give us initial data on what signals actually mattered. |
| 426 for (int32_t reason_bitfield : blacklisted_sites_reason_bitfield) { | 473 for (int32_t reason_bitfield : blacklisted_sites_reason_bitfield) { |
| 427 UMA_HISTOGRAM_ENUMERATION("Storage.BlacklistedImportantSites.Reason", | 474 UMA_HISTOGRAM_ENUMERATION("Storage.BlacklistedImportantSites.Reason", |
| 428 GetCrossedReasonFromBitfield(reason_bitfield), | 475 GetCrossedReasonFromBitfield(reason_bitfield), |
| 429 CROSSED_REASON_BOUNDARY); | 476 CROSSED_REASON_BOUNDARY); |
| 430 } | 477 } |
| 431 } | 478 } |
| 432 | 479 |
| 433 void ImportantSitesUtil::MarkOriginAsImportantForTesting(Profile* profile, | 480 void ImportantSitesUtil::MarkOriginAsImportantForTesting(Profile* profile, |
| 434 const GURL& origin) { | 481 const GURL& origin) { |
| 435 SiteEngagementScore::SetParamValuesForTesting(); | 482 SiteEngagementScore::SetParamValuesForTesting(); |
| 436 // First get data from site engagement. | 483 // First get data from site engagement. |
| 437 SiteEngagementService* site_engagement_service = | 484 SiteEngagementService* site_engagement_service = |
| 438 SiteEngagementService::Get(profile); | 485 SiteEngagementService::Get(profile); |
| 439 site_engagement_service->ResetScoreForURL( | 486 site_engagement_service->ResetScoreForURL( |
| 440 origin, SiteEngagementScore::GetMediumEngagementBoundary()); | 487 origin, SiteEngagementScore::GetMediumEngagementBoundary()); |
| 441 DCHECK(site_engagement_service->IsEngagementAtLeast( | 488 DCHECK(site_engagement_service->IsEngagementAtLeast( |
| 442 origin, blink::mojom::EngagementLevel::MEDIUM)); | 489 origin, blink::mojom::EngagementLevel::MEDIUM)); |
| 443 } | 490 } |
| OLD | NEW |