1 // Copyright 2019 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/history/domain_diversity_reporter.h"
6 
7 #include "base/metrics/histogram_macros.h"
8 #include "base/threading/thread_task_runner_handle.h"
9 #include "components/pref_registry/pref_registry_syncable.h"
10 #include "components/prefs/pref_service.h"
11 #include "content/public/browser/browser_task_traits.h"
12 #include "content/public/browser/browser_thread.h"
13 
14 namespace {
15 // The interval between two successive domain metrics reports.
16 constexpr base::TimeDelta kDomainDiversityReportingInterval =
17     base::TimeDelta::FromDays(1);
18 
19 // Pref name for the persistent timestamp of the last report. This pref is
20 // per local profile but not synced.
21 constexpr char kDomainDiversityReportingTimestamp[] =
22     "domain_diversity.last_reporting_timestamp";
23 }  // namespace
24 
DomainDiversityReporter(history::HistoryService * history_service,PrefService * prefs,base::Clock * clock)25 DomainDiversityReporter::DomainDiversityReporter(
26     history::HistoryService* history_service,
27     PrefService* prefs,
28     base::Clock* clock)
29     : history_service_(history_service),
30       prefs_(prefs),
31       clock_(clock),
32       history_service_observer_(this) {
33   DCHECK_NE(prefs_, nullptr);
34   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
35 
36   content::GetUIThreadTaskRunner({base::TaskPriority::BEST_EFFORT})
37       ->PostTask(
38           FROM_HERE,
39           base::BindOnce(&DomainDiversityReporter::MaybeComputeDomainMetrics,
40                          weak_ptr_factory_.GetWeakPtr()));
41 }
42 
43 DomainDiversityReporter::~DomainDiversityReporter() = default;
44 
45 // static
RegisterProfilePrefs(user_prefs::PrefRegistrySyncable * registry)46 void DomainDiversityReporter::RegisterProfilePrefs(
47     user_prefs::PrefRegistrySyncable* registry) {
48   registry->RegisterTimePref(kDomainDiversityReportingTimestamp, base::Time());
49 }
50 
MaybeComputeDomainMetrics()51 void DomainDiversityReporter::MaybeComputeDomainMetrics() {
52   if (history_service_->BackendLoaded()) {
53     // HistoryService is ready; proceed to start the domain metrics
54     // computation task.
55     ComputeDomainMetrics();
56   }
57   // Observe history service and start reporting as soon as
58   // the former is ready.
59   DCHECK(!history_service_observer_.IsObserving(history_service_));
60   history_service_observer_.Add(history_service_);
61 }
62 
ComputeDomainMetrics()63 void DomainDiversityReporter::ComputeDomainMetrics() {
64   base::Time time_last_report_triggered =
65       prefs_->GetTime(kDomainDiversityReportingTimestamp);
66   base::Time time_current_report_triggered = clock_->Now();
67 
68   if (time_last_report_triggered < time_current_report_triggered) {
69     // The lower boundary of all times is set at Unix epoch, since
70     // LocalMidnight() may fail on times represented by a very small value
71     // (e.g. Windows epoch).
72     if (time_last_report_triggered < base::Time::UnixEpoch())
73       time_last_report_triggered = base::Time::UnixEpoch();
74 
75     if (time_current_report_triggered < base::Time::UnixEpoch())
76       time_current_report_triggered = base::Time::UnixEpoch();
77 
78     // Will only report up to 7 days x 3 results.
79     int number_of_days_to_report = 7;
80 
81     // If the last report time is too far back in the past, simply use the
82     // highest possible value for |number_of_days_to_report| and skip its
83     // computation. This avoids calling LocalMidnight() on some very old
84     // timestamp that may cause unexpected behaviors on certain
85     // platforms/timezones (see https://crbug.com/1048145).
86     // The beginning and the end of a 7-day period may differ by at most
87     // 24 * 8 + 1(DST offset) hours; round up to FromDays(9) here.
88     if (time_current_report_triggered - time_last_report_triggered <
89         base::TimeDelta::FromDays(number_of_days_to_report + 2)) {
90       // Compute the number of days that needs to be reported for based on
91       // the last report time and current time.
92       base::TimeDelta report_time_range =
93           time_current_report_triggered.LocalMidnight() -
94           time_last_report_triggered.LocalMidnight();
95 
96       // Due to daylight saving time, |report_time_range| may not be a multiple
97       // of 24 hours. A small time offset is therefore added to
98       // |report_time_range| so that the resulting time range is guaranteed to
99       // be at least the correct number of days times 24. The number of days to
100       // report is capped at 7 days.
101       number_of_days_to_report = std::min(
102           (report_time_range + base::TimeDelta::FromHours(4)).InDaysFloored(),
103           number_of_days_to_report);
104     }
105 
106     if (number_of_days_to_report >= 1) {
107       history_service_->GetDomainDiversity(
108           /*report_time=*/time_current_report_triggered,
109           /*number_of_days_to_report=*/number_of_days_to_report,
110           /*metric_type_bitmask=*/history::kEnableLast1DayMetric |
111               history::kEnableLast7DayMetric | history::kEnableLast28DayMetric,
112           base::BindOnce(&DomainDiversityReporter::ReportDomainMetrics,
113                          weak_ptr_factory_.GetWeakPtr(),
114                          time_current_report_triggered),
115           &cancelable_task_tracker_);
116     }
117   }
118 
119   // The next reporting task is scheduled to run 24 hours later.
120   base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
121       FROM_HERE,
122       base::BindOnce(&DomainDiversityReporter::ComputeDomainMetrics,
123                      weak_ptr_factory_.GetWeakPtr()),
124       kDomainDiversityReportingInterval);
125 }
126 
ReportDomainMetrics(base::Time time_current_report_triggered,history::DomainDiversityResults result)127 void DomainDiversityReporter::ReportDomainMetrics(
128     base::Time time_current_report_triggered,
129     history::DomainDiversityResults result) {
130   // An empty DomainDiversityResults indicates that |db_| is null in
131   // HistoryBackend.
132   if (result.empty())
133     return;
134 
135   for (auto& result_one_day : result) {
136     UMA_HISTOGRAM_COUNTS_1000("History.DomainCount1Day",
137                               result_one_day.one_day_metric.value().count);
138     UMA_HISTOGRAM_COUNTS_1000("History.DomainCount7Day",
139                               result_one_day.seven_day_metric.value().count);
140     UMA_HISTOGRAM_COUNTS_1000(
141         "History.DomainCount28Day",
142         result_one_day.twenty_eight_day_metric.value().count);
143   }
144 
145   prefs_->SetTime(kDomainDiversityReportingTimestamp,
146                   time_current_report_triggered);
147 }
148 
OnHistoryServiceLoaded(history::HistoryService * history_service)149 void DomainDiversityReporter::OnHistoryServiceLoaded(
150     history::HistoryService* history_service) {
151   DCHECK_EQ(history_service, history_service_);
152   ComputeDomainMetrics();
153 }
154 
HistoryServiceBeingDeleted(history::HistoryService * history_service)155 void DomainDiversityReporter::HistoryServiceBeingDeleted(
156     history::HistoryService* history_service) {
157   history_service_observer_.RemoveAll();
158   cancelable_task_tracker_.TryCancelAll();
159 }
160