Chromium Code Reviews| 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 int times_ignored = 0; | |
|
dominickn
2017/02/03 21:14:51
Nit: move times_ignored down to line 117 (right be
dmurph
2017/02/04 00:52:25
Done.
| |
| 108 if (dict->GetDouble(kTimeLastIgnored, &last_ignored_time)) { | |
| 109 base::TimeDelta diff = | |
| 110 base::Time::Now() - base::Time::FromDoubleT(last_ignored_time); | |
| 111 if (diff >= base::TimeDelta::FromDays(kBlacklistExpirationTimeDays)) { | |
| 112 dict->SetInteger(kNumTimesIgnoredName, 0); | |
|
dominickn
2017/02/03 21:14:51
Perhaps dict->Remove(kTimeLastIgnored) as well?
dmurph
2017/02/04 00:52:25
Done.
| |
| 113 return false; | |
| 114 } | |
| 115 } | |
| 116 | |
| 117 if (dict->GetInteger(kNumTimesIgnoredName, ×_ignored) && | |
|
dominickn
2017/02/03 21:14:51
Nit: just do
return (dict->GetInteger(kNumTimesIg
dmurph
2017/02/04 00:52:25
Nice catch, thanks.
| |
| 118 times_ignored >= kTimesIgnoredForBlacklist) { | |
| 119 return true; | |
| 120 } | |
| 121 | |
| 122 return false; | |
| 123 } | |
| 124 | |
| 87 CrossedReason GetCrossedReasonFromBitfield(int32_t reason_bitfield) { | 125 CrossedReason GetCrossedReasonFromBitfield(int32_t reason_bitfield) { |
| 88 bool durable = (reason_bitfield & (1 << ImportantReason::DURABLE)) != 0; | 126 bool durable = (reason_bitfield & (1 << ImportantReason::DURABLE)) != 0; |
| 89 bool notifications = | 127 bool notifications = |
| 90 (reason_bitfield & (1 << ImportantReason::NOTIFICATIONS)) != 0; | 128 (reason_bitfield & (1 << ImportantReason::NOTIFICATIONS)) != 0; |
| 91 bool engagement = (reason_bitfield & (1 << ImportantReason::ENGAGEMENT)) != 0; | 129 bool engagement = (reason_bitfield & (1 << ImportantReason::ENGAGEMENT)) != 0; |
| 92 if (durable && notifications && engagement) | 130 if (durable && notifications && engagement) |
| 93 return CROSSED_NOTIFICATIONS_AND_DURABLE_AND_ENGAGEMENT; | 131 return CROSSED_NOTIFICATIONS_AND_DURABLE_AND_ENGAGEMENT; |
| 94 else if (notifications && durable) | 132 else if (notifications && durable) |
| 95 return CROSSED_NOTIFICATIONS_AND_DURABLE; | 133 return CROSSED_NOTIFICATIONS_AND_DURABLE; |
| 96 else if (notifications && engagement) | 134 else if (notifications && engagement) |
| (...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 191 } | 229 } |
| 192 | 230 |
| 193 std::unique_ptr<base::DictionaryValue> dict = | 231 std::unique_ptr<base::DictionaryValue> dict = |
| 194 base::DictionaryValue::From(map->GetWebsiteSetting( | 232 base::DictionaryValue::From(map->GetWebsiteSetting( |
| 195 origin, origin, CONTENT_SETTINGS_TYPE_IMPORTANT_SITE_INFO, "", | 233 origin, origin, CONTENT_SETTINGS_TYPE_IMPORTANT_SITE_INFO, "", |
| 196 nullptr)); | 234 nullptr)); |
| 197 | 235 |
| 198 if (!dict) | 236 if (!dict) |
| 199 continue; | 237 continue; |
| 200 | 238 |
| 201 int times_ignored = 0; | 239 if (ShouldSuppressItem(dict.get())) |
| 202 if (!dict->GetInteger(kNumTimesIgnoredName, ×_ignored) || | 240 ignoring_domains.insert(origin.host()); |
| 203 times_ignored < kTimesIgnoredForBlacklist) { | |
| 204 continue; | |
| 205 } | |
| 206 | |
| 207 ignoring_domains.insert(origin.host()); | |
| 208 } | 241 } |
| 209 return ignoring_domains; | 242 return ignoring_domains; |
| 210 } | 243 } |
| 211 | 244 |
| 212 void PopulateInfoMapWithSiteEngagement( | 245 void PopulateInfoMapWithSiteEngagement( |
| 213 Profile* profile, | 246 Profile* profile, |
| 214 blink::mojom::EngagementLevel minimum_engagement, | 247 blink::mojom::EngagementLevel minimum_engagement, |
| 215 std::map<GURL, double>* engagement_map, | 248 std::map<GURL, double>* engagement_map, |
| 216 base::hash_map<std::string, ImportantDomainInfo>* output) { | 249 base::hash_map<std::string, ImportantDomainInfo>* output) { |
| 217 SiteEngagementService* service = SiteEngagementService::Get(profile); | 250 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()); | 346 GURL origin(site.primary_pattern.ToString()); |
| 314 if (!AppBannerSettingsHelper::WasLaunchedRecently(profile, origin, now)) | 347 if (!AppBannerSettingsHelper::WasLaunchedRecently(profile, origin, now)) |
| 315 continue; | 348 continue; |
| 316 MaybePopulateImportantInfoForReason(origin, &content_origins, | 349 MaybePopulateImportantInfoForReason(origin, &content_origins, |
| 317 ImportantReason::HOME_SCREEN, output); | 350 ImportantReason::HOME_SCREEN, output); |
| 318 } | 351 } |
| 319 } | 352 } |
| 320 | 353 |
| 321 } // namespace | 354 } // namespace |
| 322 | 355 |
| 356 bool ImportantSitesUtil::IsDialogDisabled(Profile* profile) { | |
| 357 PrefService* service = profile->GetPrefs(); | |
| 358 DictionaryPrefUpdate update(service, prefs::kImportantSitesDialogHistory); | |
| 359 | |
| 360 return ShouldSuppressItem(update.Get()); | |
| 361 } | |
| 362 | |
| 363 void ImportantSitesUtil::RegisterProfilePrefs( | |
| 364 user_prefs::PrefRegistrySyncable* registry) { | |
| 365 registry->RegisterDictionaryPref(prefs::kImportantSitesDialogHistory); | |
| 366 } | |
| 367 | |
| 323 std::vector<ImportantDomainInfo> | 368 std::vector<ImportantDomainInfo> |
| 324 ImportantSitesUtil::GetImportantRegisterableDomains(Profile* profile, | 369 ImportantSitesUtil::GetImportantRegisterableDomains(Profile* profile, |
| 325 size_t max_results) { | 370 size_t max_results) { |
| 326 base::hash_map<std::string, ImportantDomainInfo> important_info; | 371 base::hash_map<std::string, ImportantDomainInfo> important_info; |
| 327 std::map<GURL, double> engagement_map; | 372 std::map<GURL, double> engagement_map; |
| 328 | 373 |
| 329 PopulateInfoMapWithSiteEngagement( | 374 PopulateInfoMapWithSiteEngagement( |
| 330 profile, blink::mojom::EngagementLevel::MEDIUM, &engagement_map, | 375 profile, blink::mojom::EngagementLevel::MEDIUM, &engagement_map, |
| 331 &important_info); | 376 &important_info); |
| 332 | 377 |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 378 RECORD_UMA_FOR_IMPORTANT_REASON( | 423 RECORD_UMA_FOR_IMPORTANT_REASON( |
| 379 "Storage.ImportantSites.CBDChosenReason", | 424 "Storage.ImportantSites.CBDChosenReason", |
| 380 "Storage.ImportantSites.CBDChosenReasonCount", reason_bitfield); | 425 "Storage.ImportantSites.CBDChosenReasonCount", reason_bitfield); |
| 381 } | 426 } |
| 382 for (int32_t reason_bitfield : ignored_sites_reason_bitfield) { | 427 for (int32_t reason_bitfield : ignored_sites_reason_bitfield) { |
| 383 RECORD_UMA_FOR_IMPORTANT_REASON( | 428 RECORD_UMA_FOR_IMPORTANT_REASON( |
| 384 "Storage.ImportantSites.CBDIgnoredReason", | 429 "Storage.ImportantSites.CBDIgnoredReason", |
| 385 "Storage.ImportantSites.CBDIgnoredReasonCount", reason_bitfield); | 430 "Storage.ImportantSites.CBDIgnoredReasonCount", reason_bitfield); |
| 386 } | 431 } |
| 387 | 432 |
| 388 // We use the ignored sites to update our important sites blacklist. | |
| 389 HostContentSettingsMap* map = | 433 HostContentSettingsMap* map = |
| 390 HostContentSettingsMapFactory::GetForProfile(profile); | 434 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 | 435 |
| 396 std::unique_ptr<base::DictionaryValue> dict = | 436 // We use the ignored sites to update our important sites blacklist only if |
| 397 base::DictionaryValue::From(map->GetWebsiteSetting( | 437 // the user chose to blacklist a site. |
| 398 origin, origin, CONTENT_SETTINGS_TYPE_IMPORTANT_SITE_INFO, "", | 438 if (!blacklisted_sites.empty()) { |
| 399 nullptr)); | 439 for (const std::string& ignored_site : ignored_sites) { |
| 440 GURL origin("http://" + ignored_site); | |
| 441 std::unique_ptr<base::DictionaryValue> dict = | |
| 442 base::DictionaryValue::From(map->GetWebsiteSetting( | |
| 443 origin, origin, CONTENT_SETTINGS_TYPE_IMPORTANT_SITE_INFO, "", | |
| 444 nullptr)); | |
| 400 | 445 |
| 401 int times_ignored = 0; | 446 if (!dict) |
| 402 if (dict) | 447 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 | 448 |
| 449 RecordIgnore(dict.get()); | |
| 450 | |
| 451 map->SetWebsiteSettingDefaultScope( | |
| 452 origin, origin, CONTENT_SETTINGS_TYPE_IMPORTANT_SITE_INFO, "", | |
| 453 std::move(dict)); | |
| 454 } | |
| 455 } else { | |
| 456 // Record that the user did not interact with the dialog. | |
| 457 PrefService* service = profile->GetPrefs(); | |
| 458 DictionaryPrefUpdate update(service, prefs::kImportantSitesDialogHistory); | |
| 459 RecordIgnore(update.Get()); | |
| 460 } | |
| 461 | |
| 462 // We clear our blacklist for sites that the user chose. | |
| 463 for (const std::string& blacklisted_site : blacklisted_sites) { | |
| 464 GURL origin("http://" + blacklisted_site); | |
| 465 std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); | |
| 466 dict->SetInteger(kNumTimesIgnoredName, 0); | |
| 467 dict->SetDouble(kTimeLastIgnored, base::Time::Now().ToDoubleT()); | |
|
dominickn
2017/02/03 21:14:51
Nit: since you're clearing the kNumTimesIgnored va
dmurph
2017/02/04 00:52:25
Ah true. Done.
| |
| 408 map->SetWebsiteSettingDefaultScope( | 468 map->SetWebsiteSettingDefaultScope( |
| 409 origin, origin, CONTENT_SETTINGS_TYPE_IMPORTANT_SITE_INFO, "", | 469 origin, origin, CONTENT_SETTINGS_TYPE_IMPORTANT_SITE_INFO, "", |
| 410 std::move(dict)); | 470 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 } | 471 } |
| 422 | 472 |
| 423 // Finally, record our old crossed-stats. | 473 // Finally, record our old crossed-stats. |
| 424 // Note: we don't plan on adding new metrics here, this is just for the finch | 474 // 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. | 475 // experiment to give us initial data on what signals actually mattered. |
| 426 for (int32_t reason_bitfield : blacklisted_sites_reason_bitfield) { | 476 for (int32_t reason_bitfield : blacklisted_sites_reason_bitfield) { |
| 427 UMA_HISTOGRAM_ENUMERATION("Storage.BlacklistedImportantSites.Reason", | 477 UMA_HISTOGRAM_ENUMERATION("Storage.BlacklistedImportantSites.Reason", |
| 428 GetCrossedReasonFromBitfield(reason_bitfield), | 478 GetCrossedReasonFromBitfield(reason_bitfield), |
| 429 CROSSED_REASON_BOUNDARY); | 479 CROSSED_REASON_BOUNDARY); |
| 430 } | 480 } |
| 431 } | 481 } |
| 432 | 482 |
| 433 void ImportantSitesUtil::MarkOriginAsImportantForTesting(Profile* profile, | 483 void ImportantSitesUtil::MarkOriginAsImportantForTesting(Profile* profile, |
| 434 const GURL& origin) { | 484 const GURL& origin) { |
| 435 SiteEngagementScore::SetParamValuesForTesting(); | 485 SiteEngagementScore::SetParamValuesForTesting(); |
| 436 // First get data from site engagement. | 486 // First get data from site engagement. |
| 437 SiteEngagementService* site_engagement_service = | 487 SiteEngagementService* site_engagement_service = |
| 438 SiteEngagementService::Get(profile); | 488 SiteEngagementService::Get(profile); |
| 439 site_engagement_service->ResetScoreForURL( | 489 site_engagement_service->ResetScoreForURL( |
| 440 origin, SiteEngagementScore::GetMediumEngagementBoundary()); | 490 origin, SiteEngagementScore::GetMediumEngagementBoundary()); |
| 441 DCHECK(site_engagement_service->IsEngagementAtLeast( | 491 DCHECK(site_engagement_service->IsEngagementAtLeast( |
| 442 origin, blink::mojom::EngagementLevel::MEDIUM)); | 492 origin, blink::mojom::EngagementLevel::MEDIUM)); |
| 443 } | 493 } |
| OLD | NEW |