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, &times_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