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
3 // found in the LICENSE file.
4
5 #include "chrome/browser/engagement/important_sites_util.h"
6
7 #include <algorithm>
8 #include <map>
9 #include <memory>
10 #include <set>
11 #include <unordered_set>
12 #include <utility>
13
14 #include "base/metrics/histogram_macros.h"
15 #include "base/stl_util.h"
16 #include "base/values.h"
17 #include "build/build_config.h"
18 #include "chrome/browser/banners/app_banner_settings_helper.h"
19 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
20 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
21 #include "chrome/browser/engagement/site_engagement_score.h"
22 #include "chrome/browser/engagement/site_engagement_service.h"
23 #include "chrome/browser/installable/installable_utils.h"
24 #include "chrome/browser/profiles/profile.h"
25 #include "chrome/common/pref_names.h"
26 #include "components/bookmarks/browser/bookmark_model.h"
27 #include "components/bookmarks/browser/url_and_title.h"
28 #include "components/content_settings/core/browser/host_content_settings_map.h"
29 #include "components/content_settings/core/common/content_settings.h"
30 #include "components/pref_registry/pref_registry_syncable.h"
31 #include "components/prefs/pref_service.h"
32 #include "components/prefs/scoped_user_pref_update.h"
33 #include "components/site_engagement/core/mojom/site_engagement_details.mojom.h"
34 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
35 #include "third_party/blink/public/mojom/site_engagement/site_engagement.mojom.h"
36 #include "url/gurl.h"
37 #include "url/origin.h"
38 #include "url/url_util.h"
39
40 #if defined(OS_ANDROID)
41 #include "chrome/browser/android/search_permissions/search_permissions_service.h"
42 #else
43 #include "chrome/browser/web_applications/components/web_app_id.h"
44 #include "chrome/browser/web_applications/web_app_provider.h"
45 #include "chrome/browser/web_applications/web_app_registrar.h"
46 #endif
47
48 namespace {
49 using bookmarks::BookmarkModel;
50 using bookmarks::UrlAndTitle;
51 using ImportantDomainInfo = ImportantSitesUtil::ImportantDomainInfo;
52 using ImportantReason = ImportantSitesUtil::ImportantReason;
53
54 // Note: These values are stored on both the per-site content settings
55 // dictionary and the dialog preference dictionary.
56
57 static const char kTimeLastIgnored[] = "TimeLastIgnored";
58 static const int kBlacklistExpirationTimeDays = 30 * 5;
59
60 static const char kNumTimesIgnoredName[] = "NumTimesIgnored";
61 static const int kTimesIgnoredForBlacklist = 3;
62
63 // These are the maximum # of bookmarks we can use as signals. If the user has
64 // <= kMaxBookmarks, then we just use those bookmarks. Otherwise we filter all
65 // bookmarks on site engagement > 0, sort, and trim to kMaxBookmarks.
66 static const int kMaxBookmarks = 5;
67
68 // We need this to be a macro, as the histogram macros cache their pointers
69 // after the first call, so when we change the uma name we check fail if we're
70 // just a method.
71 #define RECORD_UMA_FOR_IMPORTANT_REASON(uma_name, uma_count_name, \
72 reason_bitfield) \
73 do { \
74 int count = 0; \
75 int32_t bitfield = (reason_bitfield); \
76 for (int i = 0; i < ImportantReason::REASON_BOUNDARY; i++) { \
77 if ((bitfield >> i) & 1) { \
78 count++; \
79 UMA_HISTOGRAM_ENUMERATION((uma_name), static_cast<ImportantReason>(i), \
80 ImportantReason::REASON_BOUNDARY); \
81 } \
82 } \
83 UMA_HISTOGRAM_EXACT_LINEAR( \
84 (uma_count_name), count, \
85 static_cast<int>(ImportantReason::REASON_BOUNDARY)); \
86 } while (0)
87
88 // Do not change the values here, as they are used for UMA histograms and
89 // testing in important_sites_util_unittest.
90 enum CrossedReason {
91 CROSSED_DURABLE = 0,
92 CROSSED_NOTIFICATIONS = 1,
93 CROSSED_ENGAGEMENT = 2,
94 CROSSED_NOTIFICATIONS_AND_ENGAGEMENT = 3,
95 CROSSED_DURABLE_AND_ENGAGEMENT = 4,
96 CROSSED_NOTIFICATIONS_AND_DURABLE = 5,
97 CROSSED_NOTIFICATIONS_AND_DURABLE_AND_ENGAGEMENT = 6,
98 CROSSED_REASON_UNKNOWN = 7,
99 CROSSED_REASON_BOUNDARY
100 };
101
RecordIgnore(base::DictionaryValue * dict)102 void RecordIgnore(base::DictionaryValue* dict) {
103 int times_ignored = 0;
104 dict->GetInteger(kNumTimesIgnoredName, ×_ignored);
105 dict->SetInteger(kNumTimesIgnoredName, ++times_ignored);
106 dict->SetDouble(kTimeLastIgnored, base::Time::Now().ToDoubleT());
107 }
108
109 // If we should blacklist the item with the given dictionary ignored record.
ShouldSuppressItem(base::Value * dict)110 bool ShouldSuppressItem(base::Value* dict) {
111 base::Optional<double> last_ignored_time =
112 dict->FindDoubleKey(kTimeLastIgnored);
113 if (last_ignored_time) {
114 base::TimeDelta diff =
115 base::Time::Now() - base::Time::FromDoubleT(*last_ignored_time);
116 if (diff >= base::TimeDelta::FromDays(kBlacklistExpirationTimeDays)) {
117 dict->SetIntKey(kNumTimesIgnoredName, 0);
118 dict->RemoveKey(kTimeLastIgnored);
119 return false;
120 }
121 }
122
123 base::Optional<int> times_ignored = dict->FindIntKey(kNumTimesIgnoredName);
124 return times_ignored && *times_ignored >= kTimesIgnoredForBlacklist;
125 }
126
GetCrossedReasonFromBitfield(int32_t reason_bitfield)127 CrossedReason GetCrossedReasonFromBitfield(int32_t reason_bitfield) {
128 bool durable = (reason_bitfield & (1 << ImportantReason::DURABLE)) != 0;
129 bool notifications =
130 (reason_bitfield & (1 << ImportantReason::NOTIFICATIONS)) != 0;
131 bool engagement = (reason_bitfield & (1 << ImportantReason::ENGAGEMENT)) != 0;
132 if (durable && notifications && engagement)
133 return CROSSED_NOTIFICATIONS_AND_DURABLE_AND_ENGAGEMENT;
134 else if (notifications && durable)
135 return CROSSED_NOTIFICATIONS_AND_DURABLE;
136 else if (notifications && engagement)
137 return CROSSED_NOTIFICATIONS_AND_ENGAGEMENT;
138 else if (durable && engagement)
139 return CROSSED_DURABLE_AND_ENGAGEMENT;
140 else if (notifications)
141 return CROSSED_NOTIFICATIONS;
142 else if (durable)
143 return CROSSED_DURABLE;
144 else if (engagement)
145 return CROSSED_ENGAGEMENT;
146 return CROSSED_REASON_UNKNOWN;
147 }
148
MaybePopulateImportantInfoForReason(const GURL & origin,std::set<GURL> * visited_origins,ImportantReason reason,base::Optional<std::string> app_name,std::map<std::string,ImportantDomainInfo> * output)149 void MaybePopulateImportantInfoForReason(
150 const GURL& origin,
151 std::set<GURL>* visited_origins,
152 ImportantReason reason,
153 base::Optional<std::string> app_name,
154 std::map<std::string, ImportantDomainInfo>* output) {
155 if (!origin.is_valid() || !visited_origins->insert(origin).second)
156 return;
157 std::string registerable_domain =
158 ImportantSitesUtil::GetRegisterableDomainOrIP(origin);
159 ImportantDomainInfo& info = (*output)[registerable_domain];
160 info.reason_bitfield |= 1 << reason;
161 if (info.example_origin.is_empty()) {
162 info.registerable_domain = registerable_domain;
163 info.example_origin = origin;
164 }
165 info.app_name = app_name;
166 }
167
168 // Returns the score associated with the given reason. The order of
169 // ImportantReason does not need to correspond to the score order. The higher
170 // the score, the more important the reason is.
GetScoreForReason(ImportantReason reason)171 int GetScoreForReason(ImportantReason reason) {
172 switch (reason) {
173 case ImportantReason::ENGAGEMENT:
174 return 1 << 0;
175 case ImportantReason::DURABLE:
176 return 1 << 1;
177 case ImportantReason::BOOKMARKS:
178 return 1 << 2;
179 case ImportantReason::HOME_SCREEN:
180 return 1 << 3;
181 case ImportantReason::NOTIFICATIONS:
182 return 1 << 4;
183 case ImportantReason::REASON_BOUNDARY:
184 return 0;
185 }
186 return 0;
187 }
188
GetScoreForReasonsBitfield(int32_t reason_bitfield)189 int GetScoreForReasonsBitfield(int32_t reason_bitfield) {
190 int score = 0;
191 for (int i = 0; i < ImportantReason::REASON_BOUNDARY; i++) {
192 if ((reason_bitfield >> i) & 1) {
193 score += GetScoreForReason(static_cast<ImportantReason>(i));
194 }
195 }
196 return score;
197 }
198
199 // Returns if |a| has a higher score than |b|, so that when we sort the higher
200 // score is first.
CompareDescendingImportantInfo(const std::pair<std::string,ImportantDomainInfo> & a,const std::pair<std::string,ImportantDomainInfo> & b)201 bool CompareDescendingImportantInfo(
202 const std::pair<std::string, ImportantDomainInfo>& a,
203 const std::pair<std::string, ImportantDomainInfo>& b) {
204 int score_a = GetScoreForReasonsBitfield(a.second.reason_bitfield);
205 int score_b = GetScoreForReasonsBitfield(b.second.reason_bitfield);
206 int bitfield_diff = score_a - score_b;
207 if (bitfield_diff != 0)
208 return bitfield_diff > 0;
209 return a.second.engagement_score > b.second.engagement_score;
210 }
211
GetBlacklistedImportantDomains(Profile * profile)212 std::unordered_set<std::string> GetBlacklistedImportantDomains(
213 Profile* profile) {
214 ContentSettingsForOneType content_settings_list;
215 HostContentSettingsMap* map =
216 HostContentSettingsMapFactory::GetForProfile(profile);
217 map->GetSettingsForOneType(ContentSettingsType::IMPORTANT_SITE_INFO,
218
219 &content_settings_list);
220 std::unordered_set<std::string> ignoring_domains;
221 for (ContentSettingPatternSource& site : content_settings_list) {
222 GURL origin(site.primary_pattern.ToString());
223 if (!origin.is_valid() || base::Contains(ignoring_domains, origin.host())) {
224 continue;
225 }
226
227 if (!site.setting_value.is_dict())
228 continue;
229
230 if (ShouldSuppressItem(&site.setting_value))
231 ignoring_domains.insert(origin.host());
232 }
233 return ignoring_domains;
234 }
235
236 // Inserts origins with some engagement measure into the map, including a site
237 // engagement cutoff and recent launches from home screen.
PopulateInfoMapWithEngagement(Profile * profile,blink::mojom::EngagementLevel minimum_engagement,std::map<GURL,double> * engagement_map,std::map<std::string,ImportantDomainInfo> * output)238 void PopulateInfoMapWithEngagement(
239 Profile* profile,
240 blink::mojom::EngagementLevel minimum_engagement,
241 std::map<GURL, double>* engagement_map,
242 std::map<std::string, ImportantDomainInfo>* output) {
243 SiteEngagementService* service = SiteEngagementService::Get(profile);
244 std::vector<mojom::SiteEngagementDetails> engagement_details =
245 service->GetAllDetails();
246 std::set<GURL> content_origins;
247
248 // We can have multiple origins for a single domain, so we record the one
249 // with the highest engagement score.
250 for (const auto& detail : engagement_details) {
251 if (detail.installed_bonus > 0) {
252 MaybePopulateImportantInfoForReason(detail.origin, &content_origins,
253 ImportantReason::HOME_SCREEN,
254 base::nullopt, output);
255 }
256
257 (*engagement_map)[detail.origin] = detail.total_score;
258
259 if (!service->IsEngagementAtLeast(detail.origin, minimum_engagement))
260 continue;
261
262 std::string registerable_domain =
263 ImportantSitesUtil::GetRegisterableDomainOrIP(detail.origin);
264 ImportantDomainInfo& info = (*output)[registerable_domain];
265 if (detail.total_score > info.engagement_score) {
266 info.registerable_domain = registerable_domain;
267 info.engagement_score = detail.total_score;
268 info.example_origin = detail.origin;
269 info.reason_bitfield |= 1 << ImportantReason::ENGAGEMENT;
270 }
271 }
272 }
273
PopulateInfoMapWithContentTypeAllowed(Profile * profile,ContentSettingsType content_type,ImportantReason reason,std::map<std::string,ImportantDomainInfo> * output)274 void PopulateInfoMapWithContentTypeAllowed(
275 Profile* profile,
276 ContentSettingsType content_type,
277 ImportantReason reason,
278 std::map<std::string, ImportantDomainInfo>* output) {
279 // Grab our content settings list.
280 ContentSettingsForOneType content_settings_list;
281 HostContentSettingsMapFactory::GetForProfile(profile)->GetSettingsForOneType(
282 content_type, &content_settings_list);
283
284 // Extract a set of urls, using the primary pattern. We don't handle
285 // wildcard patterns.
286 std::set<GURL> content_origins;
287 for (const ContentSettingPatternSource& site : content_settings_list) {
288 if (site.GetContentSetting() != CONTENT_SETTING_ALLOW)
289 continue;
290 GURL url(site.primary_pattern.ToString());
291
292 #if defined(OS_ANDROID)
293 SearchPermissionsService* search_permissions_service =
294 SearchPermissionsService::Factory::GetInstance()->GetForBrowserContext(
295 profile);
296 // If the permission is controlled by the Default Search Engine then don't
297 // consider it important. The DSE gets these permissions by default.
298 if (search_permissions_service &&
299 search_permissions_service->IsPermissionControlledByDSE(
300 content_type, url::Origin::Create(url))) {
301 continue;
302 }
303 #endif
304
305 MaybePopulateImportantInfoForReason(url, &content_origins, reason,
306 base::nullopt, output);
307 }
308 }
309
PopulateInfoMapWithBookmarks(Profile * profile,const std::map<GURL,double> & engagement_map,std::map<std::string,ImportantDomainInfo> * output)310 void PopulateInfoMapWithBookmarks(
311 Profile* profile,
312 const std::map<GURL, double>& engagement_map,
313 std::map<std::string, ImportantDomainInfo>* output) {
314 SiteEngagementService* service = SiteEngagementService::Get(profile);
315 BookmarkModel* model =
316 BookmarkModelFactory::GetForBrowserContextIfExists(profile);
317 if (!model)
318 return;
319 std::vector<UrlAndTitle> untrimmed_bookmarks;
320 model->GetBookmarks(&untrimmed_bookmarks);
321
322 // Process the bookmarks and optionally trim them if we have too many.
323 std::vector<UrlAndTitle> result_bookmarks;
324 if (untrimmed_bookmarks.size() > kMaxBookmarks) {
325 std::copy_if(untrimmed_bookmarks.begin(), untrimmed_bookmarks.end(),
326 std::back_inserter(result_bookmarks),
327 [service](const UrlAndTitle& entry) {
328 return service->IsEngagementAtLeast(
329 entry.url.GetOrigin(),
330 blink::mojom::EngagementLevel::LOW);
331 });
332 // TODO(dmurph): Simplify this (and probably much more) once
333 // SiteEngagementService::GetAllDetails lands (crbug/703848), as that will
334 // allow us to remove most of these lookups and merging of signals.
335 std::sort(
336 result_bookmarks.begin(), result_bookmarks.end(),
337 [&engagement_map](const UrlAndTitle& a, const UrlAndTitle& b) {
338 auto a_it = engagement_map.find(a.url.GetOrigin());
339 auto b_it = engagement_map.find(b.url.GetOrigin());
340 double a_score = a_it == engagement_map.end() ? 0 : a_it->second;
341 double b_score = b_it == engagement_map.end() ? 0 : b_it->second;
342 return a_score > b_score;
343 });
344 if (result_bookmarks.size() > kMaxBookmarks)
345 result_bookmarks.resize(kMaxBookmarks);
346 } else {
347 result_bookmarks = std::move(untrimmed_bookmarks);
348 }
349
350 std::set<GURL> content_origins;
351 for (const UrlAndTitle& bookmark : result_bookmarks) {
352 MaybePopulateImportantInfoForReason(bookmark.url, &content_origins,
353 ImportantReason::BOOKMARKS,
354 base::nullopt, output);
355 }
356 }
357
358 // WebAppRegistrar is desktop specific, but Android does not warn users
359 // about clearing data for installed apps, so this and any functions explicitly
360 // used to warn about clearing data for installed apps can be excluded from the
361 // Android build.
362 #if !defined(OS_ANDROID)
PopulateInfoMapWithInstalledEngagedInTimePeriod(browsing_data::TimePeriod time_period,Profile * profile,std::map<std::string,ImportantDomainInfo> * output)363 void PopulateInfoMapWithInstalledEngagedInTimePeriod(
364 browsing_data::TimePeriod time_period,
365 Profile* profile,
366 std::map<std::string, ImportantDomainInfo>* output) {
367 SiteEngagementService* service = SiteEngagementService::Get(profile);
368 std::vector<mojom::SiteEngagementDetails> engagement_details =
369 service->GetAllDetailsEngagedInTimePeriod(time_period);
370 std::set<GURL> content_origins;
371
372 // Check with AppRegistrar to make sure the apps have not yet been
373 // uninstalled.
374 const web_app::AppRegistrar& registrar =
375 web_app::WebAppProvider::Get(profile)->registrar();
376 auto app_ids = registrar.GetAppIds();
377 std::map<std::string, std::string> installed_origins_map;
378 for (auto& app_id : app_ids) {
379 GURL scope = registrar.GetAppScope(app_id);
380 DCHECK(scope.is_valid());
381 auto app_name = registrar.GetAppShortName(app_id);
382 installed_origins_map.emplace(
383 std::make_pair(scope.GetOrigin().spec(), app_name));
384 }
385
386 for (const auto& detail : engagement_details) {
387 if (detail.installed_bonus > 0) {
388 auto origin_pair = installed_origins_map.find(detail.origin.spec());
389 if (origin_pair != installed_origins_map.end()) {
390 MaybePopulateImportantInfoForReason(detail.origin, &content_origins,
391 ImportantReason::HOME_SCREEN,
392 origin_pair->second, output);
393 }
394 }
395 }
396 }
397 #endif
398
399 } // namespace
400
401 ImportantDomainInfo::ImportantDomainInfo() = default;
402 ImportantDomainInfo::~ImportantDomainInfo() = default;
403 ImportantDomainInfo::ImportantDomainInfo(ImportantDomainInfo&&) = default;
404 ImportantDomainInfo& ImportantDomainInfo::operator=(ImportantDomainInfo&&) =
405 default;
406
GetRegisterableDomainOrIP(const GURL & url)407 std::string ImportantSitesUtil::GetRegisterableDomainOrIP(const GURL& url) {
408 return GetRegisterableDomainOrIPFromHost(url.host_piece());
409 }
410
GetRegisterableDomainOrIPFromHost(base::StringPiece host)411 std::string ImportantSitesUtil::GetRegisterableDomainOrIPFromHost(
412 base::StringPiece host) {
413 std::string registerable_domain =
414 net::registry_controlled_domains::GetDomainAndRegistry(
415 host, net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
416 if (registerable_domain.empty() && url::HostIsIPAddress(host))
417 registerable_domain = std::string(host);
418 return registerable_domain;
419 }
420
IsDialogDisabled(Profile * profile)421 bool ImportantSitesUtil::IsDialogDisabled(Profile* profile) {
422 PrefService* service = profile->GetPrefs();
423 DictionaryPrefUpdate update(service, prefs::kImportantSitesDialogHistory);
424
425 return ShouldSuppressItem(update.Get());
426 }
427
RegisterProfilePrefs(user_prefs::PrefRegistrySyncable * registry)428 void ImportantSitesUtil::RegisterProfilePrefs(
429 user_prefs::PrefRegistrySyncable* registry) {
430 registry->RegisterDictionaryPref(prefs::kImportantSitesDialogHistory);
431 }
432
433 // static
GetInstalledRegisterableDomains(Profile * profile)434 std::set<std::string> ImportantSitesUtil::GetInstalledRegisterableDomains(
435 Profile* profile) {
436 std::set<GURL> installed_origins = GetOriginsWithInstalledWebApps(profile);
437 std::set<std::string> registerable_domains;
438
439 for (auto& origin : installed_origins) {
440 registerable_domains.emplace(
441 ImportantSitesUtil::GetRegisterableDomainOrIP(origin));
442 }
443 return registerable_domains;
444 }
445
446 std::vector<ImportantDomainInfo>
GetImportantRegisterableDomains(Profile * profile,size_t max_results)447 ImportantSitesUtil::GetImportantRegisterableDomains(Profile* profile,
448 size_t max_results) {
449 SCOPED_UMA_HISTOGRAM_TIMER("Storage.ImportantSites.GenerationTime");
450 std::map<std::string, ImportantDomainInfo> important_info;
451 std::map<GURL, double> engagement_map;
452
453 PopulateInfoMapWithEngagement(profile, blink::mojom::EngagementLevel::MEDIUM,
454 &engagement_map, &important_info);
455
456 PopulateInfoMapWithContentTypeAllowed(
457 profile, ContentSettingsType::NOTIFICATIONS,
458 ImportantReason::NOTIFICATIONS, &important_info);
459
460 PopulateInfoMapWithContentTypeAllowed(
461 profile, ContentSettingsType::DURABLE_STORAGE, ImportantReason::DURABLE,
462 &important_info);
463
464 PopulateInfoMapWithBookmarks(profile, engagement_map, &important_info);
465
466 std::unordered_set<std::string> blacklisted_domains =
467 GetBlacklistedImportantDomains(profile);
468
469 std::vector<std::pair<std::string, ImportantDomainInfo>> items;
470 for (auto& item : important_info)
471 items.emplace_back(std::move(item));
472 std::sort(items.begin(), items.end(), &CompareDescendingImportantInfo);
473
474 std::vector<ImportantDomainInfo> final_list;
475 for (std::pair<std::string, ImportantDomainInfo>& domain_info : items) {
476 if (final_list.size() >= max_results)
477 return final_list;
478 if (blacklisted_domains.find(domain_info.first) !=
479 blacklisted_domains.end()) {
480 continue;
481 }
482 final_list.push_back(std::move(domain_info.second));
483 RECORD_UMA_FOR_IMPORTANT_REASON(
484 "Storage.ImportantSites.GeneratedReason",
485 "Storage.ImportantSites.GeneratedReasonCount",
486 domain_info.second.reason_bitfield);
487 }
488
489 return final_list;
490 }
491
492 #if !defined(OS_ANDROID)
493 std::vector<ImportantDomainInfo>
GetInstalledRegisterableDomains(browsing_data::TimePeriod time_period,Profile * profile,size_t max_results)494 ImportantSitesUtil::GetInstalledRegisterableDomains(
495 browsing_data::TimePeriod time_period,
496 Profile* profile,
497 size_t max_results) {
498 std::vector<ImportantDomainInfo> installed_domains;
499 std::map<std::string, ImportantDomainInfo> installed_app_info;
500 PopulateInfoMapWithInstalledEngagedInTimePeriod(time_period, profile,
501 &installed_app_info);
502
503 std::unordered_set<std::string> excluded_domains =
504 GetBlacklistedImportantDomains(profile);
505
506 std::vector<std::pair<std::string, ImportantDomainInfo>> items;
507 for (auto& item : installed_app_info)
508 items.emplace_back(std::move(item));
509 std::sort(items.begin(), items.end(), &CompareDescendingImportantInfo);
510
511 for (std::pair<std::string, ImportantDomainInfo>& domain_info : items) {
512 if (installed_domains.size() >= max_results)
513 break;
514 if (excluded_domains.find(domain_info.first) != excluded_domains.end())
515 continue;
516 installed_domains.push_back(std::move(domain_info.second));
517 }
518 return installed_domains;
519 }
520 #endif
521
RecordBlacklistedAndIgnoredImportantSites(Profile * profile,const std::vector<std::string> & blacklisted_sites,const std::vector<int32_t> & blacklisted_sites_reason_bitfield,const std::vector<std::string> & ignored_sites,const std::vector<int32_t> & ignored_sites_reason_bitfield)522 void ImportantSitesUtil::RecordBlacklistedAndIgnoredImportantSites(
523 Profile* profile,
524 const std::vector<std::string>& blacklisted_sites,
525 const std::vector<int32_t>& blacklisted_sites_reason_bitfield,
526 const std::vector<std::string>& ignored_sites,
527 const std::vector<int32_t>& ignored_sites_reason_bitfield) {
528 // First, record the metrics for blacklisted and ignored sites.
529 for (int32_t reason_bitfield : blacklisted_sites_reason_bitfield) {
530 RECORD_UMA_FOR_IMPORTANT_REASON(
531 "Storage.ImportantSites.CBDChosenReason",
532 "Storage.ImportantSites.CBDChosenReasonCount", reason_bitfield);
533 }
534 for (int32_t reason_bitfield : ignored_sites_reason_bitfield) {
535 RECORD_UMA_FOR_IMPORTANT_REASON(
536 "Storage.ImportantSites.CBDIgnoredReason",
537 "Storage.ImportantSites.CBDIgnoredReasonCount", reason_bitfield);
538 }
539
540 HostContentSettingsMap* map =
541 HostContentSettingsMapFactory::GetForProfile(profile);
542
543 // We use the ignored sites to update our important sites blacklist only if
544 // the user chose to blacklist a site.
545 if (!blacklisted_sites.empty()) {
546 for (const std::string& ignored_site : ignored_sites) {
547 GURL origin("http://" + ignored_site);
548 std::unique_ptr<base::DictionaryValue> dict =
549 base::DictionaryValue::From(map->GetWebsiteSetting(
550 origin, origin, ContentSettingsType::IMPORTANT_SITE_INFO,
551 nullptr));
552
553 if (!dict)
554 dict = std::make_unique<base::DictionaryValue>();
555
556 RecordIgnore(dict.get());
557
558 map->SetWebsiteSettingDefaultScope(
559 origin, origin, ContentSettingsType::IMPORTANT_SITE_INFO,
560 std::move(dict));
561 }
562 } else {
563 // Record that the user did not interact with the dialog.
564 PrefService* service = profile->GetPrefs();
565 DictionaryPrefUpdate update(service, prefs::kImportantSitesDialogHistory);
566 RecordIgnore(update.Get());
567 }
568
569 // We clear our blacklist for sites that the user chose.
570 for (const std::string& blacklisted_site : blacklisted_sites) {
571 GURL origin("http://" + blacklisted_site);
572 std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
573 dict->SetInteger(kNumTimesIgnoredName, 0);
574 dict->Remove(kTimeLastIgnored, nullptr);
575 map->SetWebsiteSettingDefaultScope(origin, origin,
576 ContentSettingsType::IMPORTANT_SITE_INFO,
577 std::move(dict));
578 }
579
580 // Finally, record our old crossed-stats.
581 // Note: we don't plan on adding new metrics here, this is just for the finch
582 // experiment to give us initial data on what signals actually mattered.
583 for (int32_t reason_bitfield : blacklisted_sites_reason_bitfield) {
584 UMA_HISTOGRAM_ENUMERATION("Storage.BlacklistedImportantSites.Reason",
585 GetCrossedReasonFromBitfield(reason_bitfield),
586 CROSSED_REASON_BOUNDARY);
587 }
588 }
589
MarkOriginAsImportantForTesting(Profile * profile,const GURL & origin)590 void ImportantSitesUtil::MarkOriginAsImportantForTesting(Profile* profile,
591 const GURL& origin) {
592 SiteEngagementScore::SetParamValuesForTesting();
593 // First get data from site engagement.
594 SiteEngagementService* site_engagement_service =
595 SiteEngagementService::Get(profile);
596 site_engagement_service->ResetBaseScoreForURL(
597 origin, SiteEngagementScore::GetMediumEngagementBoundary());
598 DCHECK(site_engagement_service->IsEngagementAtLeast(
599 origin, blink::mojom::EngagementLevel::MEDIUM));
600 }
601