1 // Copyright 2017 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/media/media_engagement_service.h"
6 
7 #include <memory>
8 #include <string>
9 #include <utility>
10 
11 #include "base/bind.h"
12 #include "base/callback_helpers.h"
13 #include "base/files/scoped_temp_dir.h"
14 #include "base/macros.h"
15 #include "base/memory/ptr_util.h"
16 #include "base/run_loop.h"
17 #include "base/test/scoped_feature_list.h"
18 #include "base/test/simple_test_clock.h"
19 #include "base/test/test_mock_time_task_runner.h"
20 #include "base/values.h"
21 #include "build/build_config.h"
22 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
23 #include "chrome/browser/history/history_service_factory.h"
24 #include "chrome/browser/media/media_engagement_score.h"
25 #include "chrome/common/chrome_switches.h"
26 #include "chrome/common/pref_names.h"
27 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
28 #include "chrome/test/base/testing_profile.h"
29 #include "components/content_settings/core/browser/content_settings_observer.h"
30 #include "components/content_settings/core/browser/host_content_settings_map.h"
31 #include "components/content_settings/core/common/content_settings.h"
32 #include "components/content_settings/core/common/content_settings_types.h"
33 #include "components/history/core/browser/history_database_params.h"
34 #include "components/history/core/browser/history_service.h"
35 #include "components/history/core/test/test_history_database.h"
36 #include "components/prefs/pref_service.h"
37 #include "content/public/browser/browser_thread.h"
38 #include "content/public/browser/navigation_entry.h"
39 #include "content/public/browser/page_navigator.h"
40 #include "content/public/browser/web_contents.h"
41 #include "content/public/test/web_contents_tester.h"
42 #include "media/base/media_switches.h"
43 #include "testing/gtest/include/gtest/gtest.h"
44 
45 namespace {
46 
47 base::FilePath g_temp_history_dir;
48 
49 // History is automatically expired after 90 days.
50 base::TimeDelta kHistoryExpirationThreshold = base::TimeDelta::FromDays(90);
51 
52 // Waits until a change is observed in media engagement content settings.
53 class MediaEngagementChangeWaiter : public content_settings::Observer {
54  public:
MediaEngagementChangeWaiter(Profile * profile)55   explicit MediaEngagementChangeWaiter(Profile* profile) : profile_(profile) {
56     HostContentSettingsMapFactory::GetForProfile(profile)->AddObserver(this);
57   }
58 
~MediaEngagementChangeWaiter()59   ~MediaEngagementChangeWaiter() override {
60     HostContentSettingsMapFactory::GetForProfile(profile_)->RemoveObserver(
61         this);
62   }
63 
64   // Overridden from content_settings::Observer:
OnContentSettingChanged(const ContentSettingsPattern & primary_pattern,const ContentSettingsPattern & secondary_pattern,ContentSettingsType content_type)65   void OnContentSettingChanged(const ContentSettingsPattern& primary_pattern,
66                                const ContentSettingsPattern& secondary_pattern,
67                                ContentSettingsType content_type) override {
68     if (content_type == ContentSettingsType::MEDIA_ENGAGEMENT)
69       Proceed();
70   }
71 
Wait()72   void Wait() { run_loop_.Run(); }
73 
74  private:
Proceed()75   void Proceed() { run_loop_.Quit(); }
76 
77   Profile* profile_;
78   base::RunLoop run_loop_;
79 
80   DISALLOW_COPY_AND_ASSIGN(MediaEngagementChangeWaiter);
81 };
82 
GetReferenceTime()83 base::Time GetReferenceTime() {
84   base::Time::Exploded exploded_reference_time;
85   exploded_reference_time.year = 2015;
86   exploded_reference_time.month = 1;
87   exploded_reference_time.day_of_month = 30;
88   exploded_reference_time.day_of_week = 5;
89   exploded_reference_time.hour = 11;
90   exploded_reference_time.minute = 0;
91   exploded_reference_time.second = 0;
92   exploded_reference_time.millisecond = 0;
93 
94   base::Time out_time;
95   EXPECT_TRUE(
96       base::Time::FromLocalExploded(exploded_reference_time, &out_time));
97   return out_time;
98 }
99 
BuildTestHistoryService(scoped_refptr<base::SequencedTaskRunner> backend_runner,content::BrowserContext * context)100 std::unique_ptr<KeyedService> BuildTestHistoryService(
101     scoped_refptr<base::SequencedTaskRunner> backend_runner,
102     content::BrowserContext* context) {
103   std::unique_ptr<history::HistoryService> service(
104       new history::HistoryService());
105   if (backend_runner)
106     service->set_backend_task_runner_for_testing(std::move(backend_runner));
107   service->Init(history::TestHistoryDatabaseParamsForPath(g_temp_history_dir));
108   return service;
109 }
110 
111 // Blocks until the HistoryBackend is completely destroyed, to ensure the
112 // destruction tasks do not interfere with a newer instance of
113 // HistoryService/HistoryBackend.
BlockUntilHistoryBackendDestroyed(Profile * profile)114 void BlockUntilHistoryBackendDestroyed(Profile* profile) {
115   history::HistoryService* history_service =
116       HistoryServiceFactory::GetForProfileWithoutCreating(profile);
117 
118   // Nothing to destroy
119   if (!history_service)
120     return;
121 
122   base::RunLoop run_loop;
123   history_service->SetOnBackendDestroyTask(run_loop.QuitClosure());
124   HistoryServiceFactory::ShutdownForProfile(profile);
125   run_loop.Run();
126 }
127 
128 }  // namespace
129 
130 class MediaEngagementServiceTest : public ChromeRenderViewHostTestHarness,
131                                    public testing::WithParamInterface<bool> {
132  public:
SetUp()133   void SetUp() override {
134     mock_time_task_runner_ =
135         base::MakeRefCounted<base::TestMockTimeTaskRunner>();
136 
137     if (GetParam()) {
138       scoped_feature_list_.InitWithFeatures(
139           {media::kRecordMediaEngagementScores,
140            media::kMediaEngagementHTTPSOnly},
141           {});
142     } else {
143       scoped_feature_list_.InitWithFeatures(
144           {media::kRecordMediaEngagementScores},
145           {media::kMediaEngagementHTTPSOnly});
146     }
147     ChromeRenderViewHostTestHarness::SetUp();
148 
149     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
150     g_temp_history_dir = temp_dir_.GetPath();
151     ConfigureHistoryService(nullptr);
152 
153     test_clock_.SetNow(GetReferenceTime());
154     service_ = base::WrapUnique(StartNewMediaEngagementService());
155   }
156 
service() const157   MediaEngagementService* service() const { return service_.get(); }
158 
StartNewMediaEngagementService()159   MediaEngagementService* StartNewMediaEngagementService() {
160     MediaEngagementService* service =
161         new MediaEngagementService(profile(), &test_clock_);
162     base::RunLoop().RunUntilIdle();
163     return service;
164   }
165 
ConfigureHistoryService(scoped_refptr<base::SequencedTaskRunner> backend_runner)166   void ConfigureHistoryService(
167       scoped_refptr<base::SequencedTaskRunner> backend_runner) {
168     HistoryServiceFactory::GetInstance()->SetTestingFactory(
169         profile(), base::BindRepeating(&BuildTestHistoryService,
170                                        std::move(backend_runner)));
171   }
172 
173   // Properly shuts down the HistoryService associated with |profile()| and then
174   // creates new one that will run using the |backend_runner|.
RestartHistoryService(scoped_refptr<base::SequencedTaskRunner> backend_runner)175   void RestartHistoryService(
176       scoped_refptr<base::SequencedTaskRunner> backend_runner) {
177     // Triggers destruction of the existing HistoryService and waits for all
178     // cleanup work to be done.
179     BlockUntilHistoryBackendDestroyed(profile());
180 
181     // Force the creation of a new HistoryService that runs its backend on
182     // |backend_runner|.
183     ConfigureHistoryService(std::move(backend_runner));
184     history::HistoryService* history = HistoryServiceFactory::GetForProfile(
185         profile(), ServiceAccessType::IMPLICIT_ACCESS);
186     history->AddObserver(service());
187   }
188 
RecordVisitAndPlaybackAndAdvanceClock(const url::Origin & origin)189   void RecordVisitAndPlaybackAndAdvanceClock(const url::Origin& origin) {
190     RecordVisit(origin);
191     AdvanceClock();
192     RecordPlayback(origin);
193   }
194 
TearDown()195   void TearDown() override {
196     service_->Shutdown();
197 
198     // Tests that run a history service that uses the mock task runner for
199     // backend processing will post tasks there during TearDown. Run them now to
200     // avoid leaks.
201     mock_time_task_runner_->RunUntilIdle();
202     service_.reset();
203 
204     ChromeRenderViewHostTestHarness::TearDown();
205   }
206 
AdvanceClock()207   void AdvanceClock() {
208     test_clock_.SetNow(Now() + base::TimeDelta::FromHours(1));
209   }
210 
RecordVisit(const url::Origin & origin)211   void RecordVisit(const url::Origin& origin) { service_->RecordVisit(origin); }
212 
RecordPlayback(const url::Origin & origin)213   void RecordPlayback(const url::Origin& origin) {
214     RecordPlaybackForService(service_.get(), origin);
215   }
216 
RecordPlaybackForService(MediaEngagementService * service,const url::Origin & origin)217   void RecordPlaybackForService(MediaEngagementService* service,
218                                 const url::Origin& origin) {
219     MediaEngagementScore score = service->CreateEngagementScore(origin);
220     score.IncrementMediaPlaybacks();
221     score.set_last_media_playback_time(service->clock()->Now());
222     score.Commit();
223   }
224 
ExpectScores(MediaEngagementService * service,const url::Origin & origin,double expected_score,int expected_visits,int expected_media_playbacks,base::Time expected_last_media_playback_time)225   void ExpectScores(MediaEngagementService* service,
226                     const url::Origin& origin,
227                     double expected_score,
228                     int expected_visits,
229                     int expected_media_playbacks,
230                     base::Time expected_last_media_playback_time) {
231     EXPECT_EQ(service->GetEngagementScore(origin), expected_score);
232     EXPECT_EQ(service->GetScoreMapForTesting()[origin], expected_score);
233 
234     MediaEngagementScore score = service->CreateEngagementScore(origin);
235     EXPECT_EQ(expected_visits, score.visits());
236     EXPECT_EQ(expected_media_playbacks, score.media_playbacks());
237     EXPECT_EQ(expected_last_media_playback_time,
238               score.last_media_playback_time());
239   }
240 
ExpectScores(const url::Origin & origin,double expected_score,int expected_visits,int expected_media_playbacks,base::Time expected_last_media_playback_time)241   void ExpectScores(const url::Origin& origin,
242                     double expected_score,
243                     int expected_visits,
244                     int expected_media_playbacks,
245                     base::Time expected_last_media_playback_time) {
246     ExpectScores(service_.get(), origin, expected_score, expected_visits,
247                  expected_media_playbacks, expected_last_media_playback_time);
248   }
249 
SetScores(const url::Origin & origin,int visits,int media_playbacks)250   void SetScores(const url::Origin& origin, int visits, int media_playbacks) {
251     MediaEngagementScore score = service_->CreateEngagementScore(origin);
252     score.SetVisits(visits);
253     score.SetMediaPlaybacks(media_playbacks);
254     score.Commit();
255   }
256 
SetLastMediaPlaybackTime(const url::Origin & origin,base::Time last_media_playback_time)257   void SetLastMediaPlaybackTime(const url::Origin& origin,
258                                 base::Time last_media_playback_time) {
259     MediaEngagementScore score = service_->CreateEngagementScore(origin);
260     score.last_media_playback_time_ = last_media_playback_time;
261     score.Commit();
262   }
263 
GetActualScore(const url::Origin & origin)264   double GetActualScore(const url::Origin& origin) {
265     return service_->CreateEngagementScore(origin).actual_score();
266   }
267 
GetScoreMapForTesting() const268   std::map<url::Origin, double> GetScoreMapForTesting() const {
269     return service_->GetScoreMapForTesting();
270   }
271 
ClearDataBetweenTime(base::Time begin,base::Time end)272   void ClearDataBetweenTime(base::Time begin, base::Time end) {
273     service_->ClearDataBetweenTime(begin, end);
274   }
275 
Now()276   base::Time Now() { return test_clock_.Now(); }
277 
TimeNotSet() const278   base::Time TimeNotSet() const { return base::Time(); }
279 
SetNow(base::Time now)280   void SetNow(base::Time now) { test_clock_.SetNow(now); }
281 
GetAllScoreDetails() const282   std::vector<media::mojom::MediaEngagementScoreDetailsPtr> GetAllScoreDetails()
283       const {
284     return service_->GetAllScoreDetails();
285   }
286 
HasHighEngagement(const url::Origin & origin) const287   bool HasHighEngagement(const url::Origin& origin) const {
288     return service_->HasHighEngagement(origin);
289   }
290 
SetSchemaVersion(int version)291   void SetSchemaVersion(int version) { service_->SetSchemaVersion(version); }
292 
GetAllStoredScores(const MediaEngagementService * service) const293   std::vector<MediaEngagementScore> GetAllStoredScores(
294       const MediaEngagementService* service) const {
295     return service->GetAllStoredScores();
296   }
297 
GetAllStoredScores() const298   std::vector<MediaEngagementScore> GetAllStoredScores() const {
299     return GetAllStoredScores(service_.get());
300   }
301 
302  protected:
303   scoped_refptr<base::TestMockTimeTaskRunner> mock_time_task_runner_;
304 
305  private:
306   base::ScopedTempDir temp_dir_;
307 
308   base::SimpleTestClock test_clock_;
309 
310   std::unique_ptr<MediaEngagementService> service_;
311 
312   base::test::ScopedFeatureList scoped_feature_list_;
313 };
314 
TEST_P(MediaEngagementServiceTest,MojoSerialization)315 TEST_P(MediaEngagementServiceTest, MojoSerialization) {
316   EXPECT_EQ(0u, GetAllScoreDetails().size());
317 
318   RecordVisitAndPlaybackAndAdvanceClock(
319       url::Origin::Create(GURL("http://www.example.com")));
320   RecordVisitAndPlaybackAndAdvanceClock(
321       url::Origin::Create(GURL("https://www.example.com")));
322 
323   EXPECT_EQ(GetParam() ? 1u : 2u, GetAllScoreDetails().size());
324 }
325 
TEST_P(MediaEngagementServiceTest,RestrictedToHTTPAndHTTPS)326 TEST_P(MediaEngagementServiceTest, RestrictedToHTTPAndHTTPS) {
327   std::vector<url::Origin> origins = {
328       url::Origin::Create(GURL("ftp://www.google.com/")),
329       url::Origin::Create(GURL("file://blah")),
330       url::Origin::Create(GURL("chrome://")),
331       url::Origin::Create(GURL("about://config")),
332       url::Origin::Create(GURL("http://example.com")),
333       url::Origin::Create(GURL("https://example.com")),
334   };
335 
336   for (const url::Origin& origin : origins) {
337     RecordVisitAndPlaybackAndAdvanceClock(origin);
338 
339     if (origin.scheme() == url::kHttpsScheme ||
340         (origin.scheme() == url::kHttpScheme && !GetParam())) {
341       ExpectScores(origin, 0.05, 1, 1, Now());
342     } else {
343       ExpectScores(origin, 0.0, 0, 0, TimeNotSet());
344     }
345   }
346 }
347 
TEST_P(MediaEngagementServiceTest,HandleRecordVisitAndPlaybackAndAdvanceClockion)348 TEST_P(MediaEngagementServiceTest,
349        HandleRecordVisitAndPlaybackAndAdvanceClockion) {
350   url::Origin origin1 = url::Origin::Create(GURL("https://www.google.com"));
351   ExpectScores(origin1, 0.0, 0, 0, TimeNotSet());
352   RecordVisitAndPlaybackAndAdvanceClock(origin1);
353   ExpectScores(origin1, 0.05, 1, 1, Now());
354 
355   RecordVisit(origin1);
356   ExpectScores(origin1, 0.05, 2, 1, Now());
357 
358   RecordPlayback(origin1);
359   ExpectScores(origin1, 0.1, 2, 2, Now());
360   base::Time origin1_time = Now();
361 
362   url::Origin origin2 = url::Origin::Create(GURL("https://www.google.co.uk"));
363   RecordVisitAndPlaybackAndAdvanceClock(origin2);
364   ExpectScores(origin2, 0.05, 1, 1, Now());
365   ExpectScores(origin1, 0.1, 2, 2, origin1_time);
366 }
367 
TEST_P(MediaEngagementServiceTest,IncognitoEngagementService)368 TEST_P(MediaEngagementServiceTest, IncognitoEngagementService) {
369   url::Origin origin1 = url::Origin::Create(GURL("https://www.google.fr/"));
370   url::Origin origin2 = url::Origin::Create(GURL("https://www.google.com/"));
371   url::Origin origin3 = url::Origin::Create(GURL("https://drive.google.com/"));
372   url::Origin origin4 = url::Origin::Create(GURL("https://maps.google.com/"));
373 
374   RecordVisitAndPlaybackAndAdvanceClock(origin1);
375   base::Time origin1_time = Now();
376   RecordVisitAndPlaybackAndAdvanceClock(origin2);
377 
378   MediaEngagementService* incognito_service =
379       MediaEngagementService::Get(profile()->GetPrimaryOTRProfile());
380   ExpectScores(incognito_service, origin1, 0.05, 1, 1, origin1_time);
381   ExpectScores(incognito_service, origin2, 0.05, 1, 1, Now());
382   ExpectScores(incognito_service, origin3, 0.0, 0, 0, TimeNotSet());
383 
384   incognito_service->RecordVisit(origin3);
385   ExpectScores(incognito_service, origin3, 0.0, 1, 0, TimeNotSet());
386   ExpectScores(origin3, 0.0, 0, 0, TimeNotSet());
387 
388   incognito_service->RecordVisit(origin2);
389   ExpectScores(incognito_service, origin2, 0.05, 2, 1, Now());
390   ExpectScores(origin2, 0.05, 1, 1, Now());
391 
392   RecordVisitAndPlaybackAndAdvanceClock(origin3);
393   ExpectScores(incognito_service, origin3, 0.0, 1, 0, TimeNotSet());
394   ExpectScores(origin3, 0.05, 1, 1, Now());
395 
396   ExpectScores(incognito_service, origin4, 0.0, 0, 0, TimeNotSet());
397   RecordVisitAndPlaybackAndAdvanceClock(origin4);
398   ExpectScores(incognito_service, origin4, 0.05, 1, 1, Now());
399   ExpectScores(origin4, 0.05, 1, 1, Now());
400 }
401 
TEST_P(MediaEngagementServiceTest,IncognitoOverrideRegularProfile)402 TEST_P(MediaEngagementServiceTest, IncognitoOverrideRegularProfile) {
403   const url::Origin kOrigin1 = url::Origin::Create(GURL("https://example.org"));
404   const url::Origin kOrigin2 = url::Origin::Create(GURL("https://example.com"));
405 
406   SetScores(kOrigin1, MediaEngagementScore::GetScoreMinVisits(), 1);
407   SetScores(kOrigin2, 1, 0);
408 
409   ExpectScores(kOrigin1, 0.05, MediaEngagementScore::GetScoreMinVisits(), 1,
410                TimeNotSet());
411   ExpectScores(kOrigin2, 0.0, 1, 0, TimeNotSet());
412 
413   MediaEngagementService* incognito_service =
414       MediaEngagementService::Get(profile()->GetPrimaryOTRProfile());
415   ExpectScores(incognito_service, kOrigin1, 0.05,
416                MediaEngagementScore::GetScoreMinVisits(), 1, TimeNotSet());
417   ExpectScores(incognito_service, kOrigin2, 0.0, 1, 0, TimeNotSet());
418 
419   // Scores should be the same in incognito and regular profile.
420   {
421     std::vector<std::pair<url::Origin, double>> kExpectedResults = {
422         {kOrigin2, 0.0},
423         {kOrigin1, 0.05},
424     };
425 
426     const auto& scores = GetAllStoredScores();
427     const auto& incognito_scores = GetAllStoredScores(incognito_service);
428 
429     EXPECT_EQ(kExpectedResults.size(), scores.size());
430     EXPECT_EQ(kExpectedResults.size(), incognito_scores.size());
431 
432     for (size_t i = 0; i < scores.size(); ++i) {
433       EXPECT_EQ(kExpectedResults[i].first, scores[i].origin());
434       EXPECT_EQ(kExpectedResults[i].second, scores[i].actual_score());
435 
436       EXPECT_EQ(kExpectedResults[i].first, incognito_scores[i].origin());
437       EXPECT_EQ(kExpectedResults[i].second, incognito_scores[i].actual_score());
438     }
439   }
440 
441   incognito_service->RecordVisit(kOrigin1);
442   RecordPlaybackForService(incognito_service, kOrigin2);
443 
444   // Score shouldn't have changed in regular profile.
445   {
446     std::vector<std::pair<url::Origin, double>> kExpectedResults = {
447         {kOrigin2, 0.0},
448         {kOrigin1, 0.05},
449     };
450 
451     const auto& scores = GetAllStoredScores();
452     EXPECT_EQ(kExpectedResults.size(), scores.size());
453 
454     for (size_t i = 0; i < scores.size(); ++i) {
455       EXPECT_EQ(kExpectedResults[i].first, scores[i].origin());
456       EXPECT_EQ(kExpectedResults[i].second, scores[i].actual_score());
457     }
458   }
459 
460   // Incognito scores should have the same number of entries but have new
461   // values.
462   {
463     std::vector<std::pair<url::Origin, double>> kExpectedResults = {
464         {kOrigin2, 0.05},
465         {kOrigin1, 1.0 / 21.0},
466     };
467 
468     const auto& scores = GetAllStoredScores(incognito_service);
469     EXPECT_EQ(kExpectedResults.size(), scores.size());
470 
471     for (size_t i = 0; i < scores.size(); ++i) {
472       EXPECT_EQ(kExpectedResults[i].first, scores[i].origin());
473       EXPECT_EQ(kExpectedResults[i].second, scores[i].actual_score());
474     }
475   }
476 }
477 
TEST_P(MediaEngagementServiceTest,CleanupOriginsOnHistoryDeletion)478 TEST_P(MediaEngagementServiceTest, CleanupOriginsOnHistoryDeletion) {
479   url::Origin origin1 = url::Origin::Create(GURL("https://www.google.com/"));
480   url::Origin origin2 = url::Origin::Create(GURL("https://drive.google.com/"));
481   url::Origin origin3 = url::Origin::Create(GURL("https://deleted.com/"));
482   url::Origin origin4 = url::Origin::Create(GURL("https://notdeleted.com"));
483 
484   GURL url1a = GURL("https://www.google.com/search?q=asdf");
485   GURL url1b = GURL("https://www.google.com/maps/search?q=asdf");
486   GURL url3a = GURL("https://deleted.com/test");
487 
488   // origin1 will have a score that is high enough to not return zero
489   // and we will ensure it has the same score. origin2 will have a score
490   // that is zero and will remain zero. origin3 will have a score
491   // and will be cleared. origin4 will have a normal score.
492   SetScores(origin1, MediaEngagementScore::GetScoreMinVisits() + 2, 14);
493   SetScores(origin2, 2, 1);
494   SetScores(origin3, 2, 1);
495   SetScores(origin4, MediaEngagementScore::GetScoreMinVisits(), 10);
496 
497   base::Time today = GetReferenceTime();
498   base::Time yesterday = GetReferenceTime() - base::TimeDelta::FromDays(1);
499   base::Time yesterday_afternoon = GetReferenceTime() -
500                                    base::TimeDelta::FromDays(1) +
501                                    base::TimeDelta::FromHours(4);
502   base::Time yesterday_week = GetReferenceTime() - base::TimeDelta::FromDays(8);
503   SetNow(today);
504 
505   history::HistoryService* history = HistoryServiceFactory::GetForProfile(
506       profile(), ServiceAccessType::IMPLICIT_ACCESS);
507 
508   history->AddPage(origin1.GetURL(), yesterday_afternoon,
509                    history::SOURCE_BROWSED);
510   history->AddPage(url1a, yesterday_afternoon, history::SOURCE_BROWSED);
511   history->AddPage(url1b, yesterday_week, history::SOURCE_BROWSED);
512   history->AddPage(origin2.GetURL(), yesterday_afternoon,
513                    history::SOURCE_BROWSED);
514   history->AddPage(origin3.GetURL(), yesterday_week, history::SOURCE_BROWSED);
515   history->AddPage(url3a, yesterday_afternoon, history::SOURCE_BROWSED);
516 
517   // Check that the scores are valid at the beginning.
518   ExpectScores(origin1, 7.0 / 11.0,
519                MediaEngagementScore::GetScoreMinVisits() + 2, 14, TimeNotSet());
520   EXPECT_EQ(14.0 / 22.0, GetActualScore(origin1));
521   ExpectScores(origin2, 0.05, 2, 1, TimeNotSet());
522   EXPECT_EQ(1 / 20.0, GetActualScore(origin2));
523   ExpectScores(origin3, 0.05, 2, 1, TimeNotSet());
524   EXPECT_EQ(1 / 20.0, GetActualScore(origin3));
525   ExpectScores(origin4, 0.5, MediaEngagementScore::GetScoreMinVisits(), 10,
526                TimeNotSet());
527   EXPECT_EQ(0.5, GetActualScore(origin4));
528 
529   {
530     MediaEngagementChangeWaiter waiter(profile());
531 
532     base::CancelableTaskTracker task_tracker;
533     // Expire origin1, url1a, origin2, and url3a's most recent visit.
534     history->ExpireHistoryBetween(std::set<GURL>(), yesterday, today,
535                                   /*user_initiated*/ true, base::DoNothing(),
536                                   &task_tracker);
537     waiter.Wait();
538 
539     // origin1 should have a score that is not zero and is the same as the old
540     // score (sometimes it may not match exactly due to rounding). origin2
541     // should have a score that is zero but it's visits and playbacks should
542     // have decreased. origin3 should have had a decrease in the number of
543     // visits. origin4 should have the old score.
544     ExpectScores(origin1, 0.6, MediaEngagementScore::GetScoreMinVisits(), 12,
545                  TimeNotSet());
546     EXPECT_EQ(12.0 / 20.0, GetActualScore(origin1));
547     ExpectScores(origin2, 0.0, 1, 0, TimeNotSet());
548     EXPECT_EQ(0, GetActualScore(origin2));
549     ExpectScores(origin3, 0.0, 1, 0, TimeNotSet());
550     ExpectScores(origin4, 0.5, MediaEngagementScore::GetScoreMinVisits(), 10,
551                  TimeNotSet());
552   }
553 
554   {
555     MediaEngagementChangeWaiter waiter(profile());
556 
557     // Expire url1b.
558     std::vector<history::ExpireHistoryArgs> expire_list;
559     history::ExpireHistoryArgs args;
560     args.urls.insert(url1b);
561     args.SetTimeRangeForOneDay(yesterday_week);
562     expire_list.push_back(args);
563 
564     base::CancelableTaskTracker task_tracker;
565     history->ExpireHistory(expire_list, base::DoNothing(), &task_tracker);
566     waiter.Wait();
567 
568     // origin1's score should have changed but the rest should remain the same.
569     ExpectScores(origin1, 0.55, MediaEngagementScore::GetScoreMinVisits() - 1,
570                  11, TimeNotSet());
571     ExpectScores(origin2, 0.0, 1, 0, TimeNotSet());
572     ExpectScores(origin3, 0.0, 1, 0, TimeNotSet());
573     ExpectScores(origin4, 0.5, MediaEngagementScore::GetScoreMinVisits(), 10,
574                  TimeNotSet());
575   }
576 
577   {
578     MediaEngagementChangeWaiter waiter(profile());
579 
580     // Expire origin3.
581     std::vector<history::ExpireHistoryArgs> expire_list;
582     history::ExpireHistoryArgs args;
583     args.urls.insert(origin3.GetURL());
584     args.SetTimeRangeForOneDay(yesterday_week);
585     expire_list.push_back(args);
586 
587     base::CancelableTaskTracker task_tracker;
588     history->ExpireHistory(expire_list, base::DoNothing(), &task_tracker);
589     waiter.Wait();
590 
591     // origin3's score should be removed but the rest should remain the same.
592     std::map<url::Origin, double> scores = GetScoreMapForTesting();
593     EXPECT_TRUE(scores.find(origin3) == scores.end());
594     ExpectScores(origin1, 0.55, MediaEngagementScore::GetScoreMinVisits() - 1,
595                  11, TimeNotSet());
596     ExpectScores(origin2, 0.0, 1, 0, TimeNotSet());
597     ExpectScores(origin3, 0.0, 0, 0, TimeNotSet());
598     ExpectScores(origin4, 0.5, MediaEngagementScore::GetScoreMinVisits(), 10,
599                  TimeNotSet());
600   }
601 }
602 
603 // The test is flaky: crbug.com/1042417.
604 #if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID)
605 #define MAYBE_CleanUpDatabaseWhenHistoryIsExpired \
606   DISABLED_CleanUpDatabaseWhenHistoryIsExpired
607 #else
608 #define MAYBE_CleanUpDatabaseWhenHistoryIsExpired \
609   CleanUpDatabaseWhenHistoryIsExpired
610 #endif
TEST_P(MediaEngagementServiceTest,MAYBE_CleanUpDatabaseWhenHistoryIsExpired)611 TEST_P(MediaEngagementServiceTest, MAYBE_CleanUpDatabaseWhenHistoryIsExpired) {
612   // |origin1| will have history that is before the expiry threshold and should
613   // not be deleted. |origin2| will have history either side of the threshold
614   // and should also not be deleted. |origin3| will have history before the
615   // threshold and should be deleted.
616   url::Origin origin1 = url::Origin::Create(GURL("https://www.google.com"));
617   url::Origin origin2 = url::Origin::Create(GURL("https://drive.google.com"));
618   url::Origin origin3 = url::Origin::Create(GURL("https://deleted.com"));
619 
620   // Populate test MEI data.
621   SetScores(origin1, 20, 20);
622   SetScores(origin2, 30, 30);
623   SetScores(origin3, 40, 40);
624 
625   base::Time today = base::Time::Now();
626   base::Time before_threshold = today - kHistoryExpirationThreshold;
627   SetNow(today);
628 
629   // Populate test history records.
630   history::HistoryService* history = HistoryServiceFactory::GetForProfile(
631       profile(), ServiceAccessType::IMPLICIT_ACCESS);
632 
633   history->AddPage(origin1.GetURL(), today, history::SOURCE_BROWSED);
634   history->AddPage(origin2.GetURL(), today, history::SOURCE_BROWSED);
635   history->AddPage(origin2.GetURL(), before_threshold, history::SOURCE_BROWSED);
636   history->AddPage(origin3.GetURL(), before_threshold, history::SOURCE_BROWSED);
637 
638   // Expire history older than |threshold|.
639   MediaEngagementChangeWaiter waiter(profile());
640   RestartHistoryService(mock_time_task_runner_);
641 
642   // From this point profile() is using a new HistoryService that runs on
643   // mock time.
644 
645   // Now, fast forward time to ensure that the expiration job is completed. This
646   // will start by triggering the backend initialization. 30 seconds is the
647   // value of kExpirationDelaySec.
648   mock_time_task_runner_->FastForwardBy(base::TimeDelta::FromSeconds(30));
649   waiter.Wait();
650 
651   // Check the scores for the test origins.
652   ExpectScores(origin1, 1.0, 20, 20, TimeNotSet());
653   ExpectScores(origin2, 1.0, 30, 30, TimeNotSet());
654   ExpectScores(origin3, 0, 0, 0, TimeNotSet());
655 }
656 
TEST_P(MediaEngagementServiceTest,CleanUpDatabaseWhenHistoryIsDeleted)657 TEST_P(MediaEngagementServiceTest, CleanUpDatabaseWhenHistoryIsDeleted) {
658   url::Origin origin1 = url::Origin::Create(GURL("https://www.google.com/"));
659   url::Origin origin2 = url::Origin::Create(GURL("https://drive.google.com/"));
660   url::Origin origin3 = url::Origin::Create(GURL("https://deleted.com/"));
661   url::Origin origin4 = url::Origin::Create(GURL("https://notdeleted.com"));
662 
663   GURL url1a = GURL("https://www.google.com/search?q=asdf");
664   GURL url1b = GURL("https://www.google.com/maps/search?q=asdf");
665   GURL url3a = GURL("https://deleted.com/test");
666 
667   // origin1 will have a score that is high enough to not return zero
668   // and we will ensure it has the same score. origin2 will have a score
669   // that is zero and will remain zero. origin3 will have a score
670   // and will be cleared. origin4 will have a normal score.
671   SetScores(origin1, MediaEngagementScore::GetScoreMinVisits() + 2, 14);
672   SetScores(origin2, 2, 1);
673   SetScores(origin3, 2, 1);
674   SetScores(origin4, MediaEngagementScore::GetScoreMinVisits(), 10);
675 
676   base::Time today = GetReferenceTime();
677   base::Time yesterday_afternoon = GetReferenceTime() -
678                                    base::TimeDelta::FromDays(1) +
679                                    base::TimeDelta::FromHours(4);
680   base::Time yesterday_week = GetReferenceTime() - base::TimeDelta::FromDays(8);
681   SetNow(today);
682 
683   history::HistoryService* history = HistoryServiceFactory::GetForProfile(
684       profile(), ServiceAccessType::IMPLICIT_ACCESS);
685 
686   history->AddPage(origin1.GetURL(), yesterday_afternoon,
687                    history::SOURCE_BROWSED);
688   history->AddPage(url1a, yesterday_afternoon, history::SOURCE_BROWSED);
689   history->AddPage(url1b, yesterday_week, history::SOURCE_BROWSED);
690   history->AddPage(origin2.GetURL(), yesterday_afternoon,
691                    history::SOURCE_BROWSED);
692   history->AddPage(origin3.GetURL(), yesterday_week, history::SOURCE_BROWSED);
693   history->AddPage(url3a, yesterday_afternoon, history::SOURCE_BROWSED);
694 
695   // Check that the scores are valid at the beginning.
696   ExpectScores(origin1, 7.0 / 11.0,
697                MediaEngagementScore::GetScoreMinVisits() + 2, 14, TimeNotSet());
698   EXPECT_EQ(14.0 / 22.0, GetActualScore(origin1));
699   ExpectScores(origin2, 0.05, 2, 1, TimeNotSet());
700   EXPECT_EQ(1 / 20.0, GetActualScore(origin2));
701   ExpectScores(origin3, 0.05, 2, 1, TimeNotSet());
702   EXPECT_EQ(1 / 20.0, GetActualScore(origin3));
703   ExpectScores(origin4, 0.5, MediaEngagementScore::GetScoreMinVisits(), 10,
704                TimeNotSet());
705   EXPECT_EQ(0.5, GetActualScore(origin4));
706 
707   {
708     base::RunLoop run_loop;
709     base::CancelableTaskTracker task_tracker;
710     // Clear all history.
711     history->ExpireHistoryBetween(std::set<GURL>(), base::Time(), base::Time(),
712                                   /*user_initiated*/ true,
713                                   run_loop.QuitClosure(), &task_tracker);
714     run_loop.Run();
715 
716     // origin1 should have a score that is not zero and is the same as the old
717     // score (sometimes it may not match exactly due to rounding). origin2
718     // should have a score that is zero but it's visits and playbacks should
719     // have decreased. origin3 should have had a decrease in the number of
720     // visits. origin4 should have the old score.
721     ExpectScores(origin1, 0.0, 0, 0, TimeNotSet());
722     EXPECT_EQ(0, GetActualScore(origin1));
723     ExpectScores(origin2, 0.0, 0, 0, TimeNotSet());
724     EXPECT_EQ(0, GetActualScore(origin2));
725     ExpectScores(origin3, 0.0, 0, 0, TimeNotSet());
726     ExpectScores(origin4, 0.0, 0, 0, TimeNotSet());
727   }
728 }
729 
TEST_P(MediaEngagementServiceTest,HistoryExpirationIsNoOp)730 TEST_P(MediaEngagementServiceTest, HistoryExpirationIsNoOp) {
731   url::Origin origin1 = url::Origin::Create(GURL("https://www.google.com/"));
732   url::Origin origin2 = url::Origin::Create(GURL("https://drive.google.com/"));
733   url::Origin origin3 = url::Origin::Create(GURL("https://deleted.com/"));
734   url::Origin origin4 = url::Origin::Create(GURL("https://notdeleted.com"));
735 
736   GURL url1a = GURL("https://www.google.com/search?q=asdf");
737   GURL url1b = GURL("https://www.google.com/maps/search?q=asdf");
738   GURL url3a = GURL("https://deleted.com/test");
739 
740   SetScores(origin1, MediaEngagementScore::GetScoreMinVisits() + 2, 14);
741   SetScores(origin2, 2, 1);
742   SetScores(origin3, 2, 1);
743   SetScores(origin4, MediaEngagementScore::GetScoreMinVisits(), 10);
744 
745   ExpectScores(origin1, 7.0 / 11.0,
746                MediaEngagementScore::GetScoreMinVisits() + 2, 14, TimeNotSet());
747   EXPECT_EQ(14.0 / 22.0, GetActualScore(origin1));
748   ExpectScores(origin2, 0.05, 2, 1, TimeNotSet());
749   EXPECT_EQ(1 / 20.0, GetActualScore(origin2));
750   ExpectScores(origin3, 0.05, 2, 1, TimeNotSet());
751   EXPECT_EQ(1 / 20.0, GetActualScore(origin3));
752   ExpectScores(origin4, 0.5, MediaEngagementScore::GetScoreMinVisits(), 10,
753                TimeNotSet());
754   EXPECT_EQ(0.5, GetActualScore(origin4));
755 
756   {
757     history::HistoryService* history = HistoryServiceFactory::GetForProfile(
758         profile(), ServiceAccessType::IMPLICIT_ACCESS);
759 
760     service()->OnURLsDeleted(
761         history, history::DeletionInfo(history::DeletionTimeRange::Invalid(),
762                                        true, history::URLRows(),
763                                        std::set<GURL>(), base::nullopt));
764 
765     // Same as above, nothing should have changed.
766     ExpectScores(origin1, 7.0 / 11.0,
767                  MediaEngagementScore::GetScoreMinVisits() + 2, 14,
768                  TimeNotSet());
769     EXPECT_EQ(14.0 / 22.0, GetActualScore(origin1));
770     ExpectScores(origin2, 0.05, 2, 1, TimeNotSet());
771     EXPECT_EQ(1 / 20.0, GetActualScore(origin2));
772     ExpectScores(origin3, 0.05, 2, 1, TimeNotSet());
773     EXPECT_EQ(1 / 20.0, GetActualScore(origin3));
774     ExpectScores(origin4, 0.5, MediaEngagementScore::GetScoreMinVisits(), 10,
775                  TimeNotSet());
776     EXPECT_EQ(0.5, GetActualScore(origin4));
777   }
778 }
779 
TEST_P(MediaEngagementServiceTest,CleanupDataOnSiteDataCleanup_OutsideBoundary)780 TEST_P(MediaEngagementServiceTest,
781        CleanupDataOnSiteDataCleanup_OutsideBoundary) {
782   url::Origin origin = url::Origin::Create(GURL("https://www.google.com"));
783 
784   base::Time today = GetReferenceTime();
785   SetNow(today);
786 
787   SetScores(origin, 1, 1);
788   SetLastMediaPlaybackTime(origin, today);
789 
790   ClearDataBetweenTime(today - base::TimeDelta::FromDays(2),
791                        today - base::TimeDelta::FromDays(1));
792   ExpectScores(origin, 0.05, 1, 1, today);
793 }
794 
TEST_P(MediaEngagementServiceTest,CleanupDataOnSiteDataCleanup_WithinBoundary)795 TEST_P(MediaEngagementServiceTest,
796        CleanupDataOnSiteDataCleanup_WithinBoundary) {
797   url::Origin origin1 = url::Origin::Create(GURL("https://www.google.com"));
798   url::Origin origin2 = url::Origin::Create(GURL("https://www.google.co.uk"));
799 
800   base::Time today = GetReferenceTime();
801   base::Time yesterday = today - base::TimeDelta::FromDays(1);
802   base::Time two_days_ago = today - base::TimeDelta::FromDays(2);
803   SetNow(today);
804 
805   SetScores(origin1, 1, 1);
806   SetScores(origin2, 1, 1);
807   SetLastMediaPlaybackTime(origin1, yesterday);
808   SetLastMediaPlaybackTime(origin2, two_days_ago);
809 
810   ClearDataBetweenTime(two_days_ago, yesterday);
811   ExpectScores(origin1, 0, 0, 0, TimeNotSet());
812   ExpectScores(origin2, 0, 0, 0, TimeNotSet());
813 }
814 
TEST_P(MediaEngagementServiceTest,CleanupDataOnSiteDataCleanup_NoTimeSet)815 TEST_P(MediaEngagementServiceTest, CleanupDataOnSiteDataCleanup_NoTimeSet) {
816   url::Origin origin = url::Origin::Create(GURL("https://www.google.com"));
817 
818   base::Time today = GetReferenceTime();
819 
820   SetNow(GetReferenceTime());
821   SetScores(origin, 1, 0);
822 
823   ClearDataBetweenTime(today - base::TimeDelta::FromDays(2),
824                        today - base::TimeDelta::FromDays(1));
825   ExpectScores(origin, 0.0, 1, 0, TimeNotSet());
826 }
827 
TEST_P(MediaEngagementServiceTest,CleanupDataOnSiteDataCleanup_All)828 TEST_P(MediaEngagementServiceTest, CleanupDataOnSiteDataCleanup_All) {
829   url::Origin origin1 = url::Origin::Create(GURL("https://www.google.com"));
830   url::Origin origin2 = url::Origin::Create(GURL("https://www.google.co.uk"));
831 
832   base::Time today = GetReferenceTime();
833   base::Time yesterday = today - base::TimeDelta::FromDays(1);
834   base::Time two_days_ago = today - base::TimeDelta::FromDays(2);
835   SetNow(today);
836 
837   SetScores(origin1, 1, 1);
838   SetScores(origin2, 1, 1);
839   SetLastMediaPlaybackTime(origin1, yesterday);
840   SetLastMediaPlaybackTime(origin2, two_days_ago);
841 
842   ClearDataBetweenTime(base::Time(), base::Time::Max());
843   ExpectScores(origin1, 0, 0, 0, TimeNotSet());
844   ExpectScores(origin2, 0, 0, 0, TimeNotSet());
845 }
846 
TEST_P(MediaEngagementServiceTest,HasHighEngagement)847 TEST_P(MediaEngagementServiceTest, HasHighEngagement) {
848   url::Origin origin1 = url::Origin::Create(GURL("https://www.google.com"));
849   url::Origin origin2 = url::Origin::Create(GURL("https://www.google.co.uk"));
850   url::Origin origin3 = url::Origin::Create(GURL("https://www.example.com"));
851 
852   SetScores(origin1, 20, 15);
853   SetScores(origin2, 20, 4);
854 
855   EXPECT_TRUE(HasHighEngagement(origin1));
856   EXPECT_FALSE(HasHighEngagement(origin2));
857   EXPECT_FALSE(HasHighEngagement(origin3));
858 }
859 
TEST_P(MediaEngagementServiceTest,SchemaVersion_Changed)860 TEST_P(MediaEngagementServiceTest, SchemaVersion_Changed) {
861   url::Origin origin = url::Origin::Create(GURL("https://www.google.com"));
862   SetScores(origin, 1, 2);
863 
864   SetSchemaVersion(0);
865   std::unique_ptr<MediaEngagementService> new_service =
866       base::WrapUnique<MediaEngagementService>(
867           StartNewMediaEngagementService());
868 
869   ExpectScores(new_service.get(), origin, 0.0, 0, 0, TimeNotSet());
870   new_service->Shutdown();
871 }
872 
TEST_P(MediaEngagementServiceTest,SchemaVersion_Same)873 TEST_P(MediaEngagementServiceTest, SchemaVersion_Same) {
874   url::Origin origin = url::Origin::Create(GURL("https://www.google.com"));
875   SetScores(origin, 1, 2);
876 
877   std::unique_ptr<MediaEngagementService> new_service =
878       base::WrapUnique<MediaEngagementService>(
879           StartNewMediaEngagementService());
880 
881   ExpectScores(new_service.get(), origin, 0.1, 1, 2, TimeNotSet());
882   new_service->Shutdown();
883 }
884 
885 INSTANTIATE_TEST_SUITE_P(All, MediaEngagementServiceTest, ::testing::Bool());
886 
887 class MediaEngagementServiceEnabledTest
888     : public ChromeRenderViewHostTestHarness {};
889 
TEST_F(MediaEngagementServiceEnabledTest,IsEnabled)890 TEST_F(MediaEngagementServiceEnabledTest, IsEnabled) {
891 #if defined(OS_ANDROID)
892   // Make sure these flags are disabled on Android
893   EXPECT_FALSE(base::FeatureList::IsEnabled(
894       media::kMediaEngagementBypassAutoplayPolicies));
895   EXPECT_FALSE(
896       base::FeatureList::IsEnabled(media::kPreloadMediaEngagementData));
897 #else
898   EXPECT_TRUE(base::FeatureList::IsEnabled(
899       media::kMediaEngagementBypassAutoplayPolicies));
900   EXPECT_TRUE(base::FeatureList::IsEnabled(media::kPreloadMediaEngagementData));
901 #endif
902 }
903