blob: 716f3c951e34534de1066a113dd42238c5c2ec04 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/media/media_engagement_service.h"
#include <memory>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/files/scoped_temp_dir.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/simple_test_clock.h"
#include "base/test/test_mock_time_task_runner.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/media/media_engagement_score.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "chrome/test/base/testing_profile.h"
#include "components/content_settings/core/browser/content_settings_observer.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/history/core/browser/history_database_params.h"
#include "components/history/core/browser/history_service.h"
#include "components/history/core/test/test_history_database.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/page_navigator.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/web_contents_tester.h"
#include "media/base/media_switches.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
base::FilePath g_temp_history_dir;
// History is automatically expired after 90 days.
base::TimeDelta kHistoryExpirationThreshold = base::TimeDelta::FromDays(90);
// Waits until a change is observed in media engagement content settings.
class MediaEngagementChangeWaiter : public content_settings::Observer {
public:
explicit MediaEngagementChangeWaiter(Profile* profile) : profile_(profile) {
HostContentSettingsMapFactory::GetForProfile(profile)->AddObserver(this);
}
~MediaEngagementChangeWaiter() override {
HostContentSettingsMapFactory::GetForProfile(profile_)->RemoveObserver(
this);
}
// Overridden from content_settings::Observer:
void OnContentSettingChanged(
const ContentSettingsPattern& primary_pattern,
const ContentSettingsPattern& secondary_pattern,
ContentSettingsType content_type,
const std::string& resource_identifier) override {
if (content_type == ContentSettingsType::MEDIA_ENGAGEMENT)
Proceed();
}
void Wait() { run_loop_.Run(); }
private:
void Proceed() { run_loop_.Quit(); }
Profile* profile_;
base::RunLoop run_loop_;
DISALLOW_COPY_AND_ASSIGN(MediaEngagementChangeWaiter);
};
base::Time GetReferenceTime() {
base::Time::Exploded exploded_reference_time;
exploded_reference_time.year = 2015;
exploded_reference_time.month = 1;
exploded_reference_time.day_of_month = 30;
exploded_reference_time.day_of_week = 5;
exploded_reference_time.hour = 11;
exploded_reference_time.minute = 0;
exploded_reference_time.second = 0;
exploded_reference_time.millisecond = 0;
base::Time out_time;
EXPECT_TRUE(
base::Time::FromLocalExploded(exploded_reference_time, &out_time));
return out_time;
}
std::unique_ptr<KeyedService> BuildTestHistoryService(
scoped_refptr<base::SequencedTaskRunner> backend_runner,
content::BrowserContext* context) {
std::unique_ptr<history::HistoryService> service(
new history::HistoryService());
if (backend_runner)
service->set_backend_task_runner_for_testing(std::move(backend_runner));
service->Init(history::TestHistoryDatabaseParamsForPath(g_temp_history_dir));
return service;
}
// Blocks until the HistoryBackend is completely destroyed, to ensure the
// destruction tasks do not interfere with a newer instance of
// HistoryService/HistoryBackend.
void BlockUntilHistoryBackendDestroyed(Profile* profile) {
history::HistoryService* history_service =
HistoryServiceFactory::GetForProfileWithoutCreating(profile);
// Nothing to destroy
if (!history_service)
return;
base::RunLoop run_loop;
history_service->SetOnBackendDestroyTask(run_loop.QuitClosure());
HistoryServiceFactory::ShutdownForProfile(profile);
run_loop.Run();
}
} // namespace
class MediaEngagementServiceTest : public ChromeRenderViewHostTestHarness,
public testing::WithParamInterface<bool> {
public:
void SetUp() override {
mock_time_task_runner_ =
base::MakeRefCounted<base::TestMockTimeTaskRunner>();
if (GetParam()) {
scoped_feature_list_.InitWithFeatures(
{media::kRecordMediaEngagementScores,
media::kMediaEngagementHTTPSOnly},
{});
} else {
scoped_feature_list_.InitWithFeatures(
{media::kRecordMediaEngagementScores},
{media::kMediaEngagementHTTPSOnly});
}
ChromeRenderViewHostTestHarness::SetUp();
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
g_temp_history_dir = temp_dir_.GetPath();
ConfigureHistoryService(nullptr);
test_clock_.SetNow(GetReferenceTime());
service_ = base::WrapUnique(StartNewMediaEngagementService());
}
MediaEngagementService* service() const { return service_.get(); }
MediaEngagementService* StartNewMediaEngagementService() {
MediaEngagementService* service =
new MediaEngagementService(profile(), &test_clock_);
base::RunLoop().RunUntilIdle();
return service;
}
void ConfigureHistoryService(
scoped_refptr<base::SequencedTaskRunner> backend_runner) {
HistoryServiceFactory::GetInstance()->SetTestingFactory(
profile(), base::BindRepeating(&BuildTestHistoryService,
std::move(backend_runner)));
}
// Properly shuts down the HistoryService associated with |profile()| and then
// creates new one that will run using the |backend_runner|.
void RestartHistoryService(
scoped_refptr<base::SequencedTaskRunner> backend_runner) {
// Triggers destruction of the existing HistoryService and waits for all
// cleanup work to be done.
BlockUntilHistoryBackendDestroyed(profile());
// Force the creation of a new HistoryService that runs its backend on
// |backend_runner|.
ConfigureHistoryService(std::move(backend_runner));
history::HistoryService* history = HistoryServiceFactory::GetForProfile(
profile(), ServiceAccessType::IMPLICIT_ACCESS);
history->AddObserver(service());
}
void RecordVisitAndPlaybackAndAdvanceClock(const url::Origin& origin) {
RecordVisit(origin);
AdvanceClock();
RecordPlayback(origin);
}
void TearDown() override {
service_->Shutdown();
// Tests that run a history service that uses the mock task runner for
// backend processing will post tasks there during TearDown. Run them now to
// avoid leaks.
mock_time_task_runner_->RunUntilIdle();
service_.reset();
ChromeRenderViewHostTestHarness::TearDown();
}
void AdvanceClock() {
test_clock_.SetNow(Now() + base::TimeDelta::FromHours(1));
}
void RecordVisit(const url::Origin& origin) { service_->RecordVisit(origin); }
void RecordPlayback(const url::Origin& origin) {
RecordPlaybackForService(service_.get(), origin);
}
void RecordPlaybackForService(MediaEngagementService* service,
const url::Origin& origin) {
MediaEngagementScore score = service->CreateEngagementScore(origin);
score.IncrementMediaPlaybacks();
score.set_last_media_playback_time(service->clock()->Now());
score.Commit();
}
void ExpectScores(MediaEngagementService* service,
const url::Origin& origin,
double expected_score,
int expected_visits,
int expected_media_playbacks,
base::Time expected_last_media_playback_time) {
EXPECT_EQ(service->GetEngagementScore(origin), expected_score);
EXPECT_EQ(service->GetScoreMapForTesting()[origin], expected_score);
MediaEngagementScore score = service->CreateEngagementScore(origin);
EXPECT_EQ(expected_visits, score.visits());
EXPECT_EQ(expected_media_playbacks, score.media_playbacks());
EXPECT_EQ(expected_last_media_playback_time,
score.last_media_playback_time());
}
void ExpectScores(const url::Origin& origin,
double expected_score,
int expected_visits,
int expected_media_playbacks,
base::Time expected_last_media_playback_time) {
ExpectScores(service_.get(), origin, expected_score, expected_visits,
expected_media_playbacks, expected_last_media_playback_time);
}
void SetScores(const url::Origin& origin, int visits, int media_playbacks) {
MediaEngagementScore score = service_->CreateEngagementScore(origin);
score.SetVisits(visits);
score.SetMediaPlaybacks(media_playbacks);
score.Commit();
}
void SetLastMediaPlaybackTime(const url::Origin& origin,
base::Time last_media_playback_time) {
MediaEngagementScore score = service_->CreateEngagementScore(origin);
score.last_media_playback_time_ = last_media_playback_time;
score.Commit();
}
double GetActualScore(const url::Origin& origin) {
return service_->CreateEngagementScore(origin).actual_score();
}
std::map<url::Origin, double> GetScoreMapForTesting() const {
return service_->GetScoreMapForTesting();
}
void ClearDataBetweenTime(base::Time begin, base::Time end) {
service_->ClearDataBetweenTime(begin, end);
}
base::Time Now() { return test_clock_.Now(); }
base::Time TimeNotSet() const { return base::Time(); }
void SetNow(base::Time now) { test_clock_.SetNow(now); }
std::vector<media::mojom::MediaEngagementScoreDetailsPtr> GetAllScoreDetails()
const {
return service_->GetAllScoreDetails();
}
bool HasHighEngagement(const url::Origin& origin) const {
return service_->HasHighEngagement(origin);
}
void SetSchemaVersion(int version) { service_->SetSchemaVersion(version); }
std::vector<MediaEngagementScore> GetAllStoredScores(
const MediaEngagementService* service) const {
return service->GetAllStoredScores();
}
std::vector<MediaEngagementScore> GetAllStoredScores() const {
return GetAllStoredScores(service_.get());
}
protected:
scoped_refptr<base::TestMockTimeTaskRunner> mock_time_task_runner_;
private:
base::ScopedTempDir temp_dir_;
base::SimpleTestClock test_clock_;
std::unique_ptr<MediaEngagementService> service_;
base::test::ScopedFeatureList scoped_feature_list_;
};
TEST_P(MediaEngagementServiceTest, MojoSerialization) {
EXPECT_EQ(0u, GetAllScoreDetails().size());
RecordVisitAndPlaybackAndAdvanceClock(
url::Origin::Create(GURL("http://www.example.com")));
RecordVisitAndPlaybackAndAdvanceClock(
url::Origin::Create(GURL("https://www.example.com")));
EXPECT_EQ(GetParam() ? 1u : 2u, GetAllScoreDetails().size());
}
TEST_P(MediaEngagementServiceTest, RestrictedToHTTPAndHTTPS) {
std::vector<url::Origin> origins = {
url::Origin::Create(GURL("ftp://www.google.com/")),
url::Origin::Create(GURL("file://blah")),
url::Origin::Create(GURL("chrome://")),
url::Origin::Create(GURL("about://config")),
url::Origin::Create(GURL("http://example.com")),
url::Origin::Create(GURL("https://example.com")),
};
for (const url::Origin& origin : origins) {
RecordVisitAndPlaybackAndAdvanceClock(origin);
if (origin.scheme() == url::kHttpsScheme ||
(origin.scheme() == url::kHttpScheme && !GetParam())) {
ExpectScores(origin, 0.05, 1, 1, Now());
} else {
ExpectScores(origin, 0.0, 0, 0, TimeNotSet());
}
}
}
TEST_P(MediaEngagementServiceTest,
HandleRecordVisitAndPlaybackAndAdvanceClockion) {
url::Origin origin1 = url::Origin::Create(GURL("https://www.google.com"));
ExpectScores(origin1, 0.0, 0, 0, TimeNotSet());
RecordVisitAndPlaybackAndAdvanceClock(origin1);
ExpectScores(origin1, 0.05, 1, 1, Now());
RecordVisit(origin1);
ExpectScores(origin1, 0.05, 2, 1, Now());
RecordPlayback(origin1);
ExpectScores(origin1, 0.1, 2, 2, Now());
base::Time origin1_time = Now();
url::Origin origin2 = url::Origin::Create(GURL("https://www.google.co.uk"));
RecordVisitAndPlaybackAndAdvanceClock(origin2);
ExpectScores(origin2, 0.05, 1, 1, Now());
ExpectScores(origin1, 0.1, 2, 2, origin1_time);
}
TEST_P(MediaEngagementServiceTest, IncognitoEngagementService) {
url::Origin origin1 = url::Origin::Create(GURL("https://www.google.fr/"));
url::Origin origin2 = url::Origin::Create(GURL("https://www.google.com/"));
url::Origin origin3 = url::Origin::Create(GURL("https://drive.google.com/"));
url::Origin origin4 = url::Origin::Create(GURL("https://maps.google.com/"));
RecordVisitAndPlaybackAndAdvanceClock(origin1);
base::Time origin1_time = Now();
RecordVisitAndPlaybackAndAdvanceClock(origin2);
MediaEngagementService* incognito_service =
MediaEngagementService::Get(profile()->GetPrimaryOTRProfile());
ExpectScores(incognito_service, origin1, 0.05, 1, 1, origin1_time);
ExpectScores(incognito_service, origin2, 0.05, 1, 1, Now());
ExpectScores(incognito_service, origin3, 0.0, 0, 0, TimeNotSet());
incognito_service->RecordVisit(origin3);
ExpectScores(incognito_service, origin3, 0.0, 1, 0, TimeNotSet());
ExpectScores(origin3, 0.0, 0, 0, TimeNotSet());
incognito_service->RecordVisit(origin2);
ExpectScores(incognito_service, origin2, 0.05, 2, 1, Now());
ExpectScores(origin2, 0.05, 1, 1, Now());
RecordVisitAndPlaybackAndAdvanceClock(origin3);
ExpectScores(incognito_service, origin3, 0.0, 1, 0, TimeNotSet());
ExpectScores(origin3, 0.05, 1, 1, Now());
ExpectScores(incognito_service, origin4, 0.0, 0, 0, TimeNotSet());
RecordVisitAndPlaybackAndAdvanceClock(origin4);
ExpectScores(incognito_service, origin4, 0.05, 1, 1, Now());
ExpectScores(origin4, 0.05, 1, 1, Now());
}
TEST_P(MediaEngagementServiceTest, IncognitoOverrideRegularProfile) {
const url::Origin kOrigin1 = url::Origin::Create(GURL("https://example.org"));
const url::Origin kOrigin2 = url::Origin::Create(GURL("https://example.com"));
SetScores(kOrigin1, MediaEngagementScore::GetScoreMinVisits(), 1);
SetScores(kOrigin2, 1, 0);
ExpectScores(kOrigin1, 0.05, MediaEngagementScore::GetScoreMinVisits(), 1,
TimeNotSet());
ExpectScores(kOrigin2, 0.0, 1, 0, TimeNotSet());
MediaEngagementService* incognito_service =
MediaEngagementService::Get(profile()->GetPrimaryOTRProfile());
ExpectScores(incognito_service, kOrigin1, 0.05,
MediaEngagementScore::GetScoreMinVisits(), 1, TimeNotSet());
ExpectScores(incognito_service, kOrigin2, 0.0, 1, 0, TimeNotSet());
// Scores should be the same in incognito and regular profile.
{
std::vector<std::pair<url::Origin, double>> kExpectedResults = {
{kOrigin2, 0.0},
{kOrigin1, 0.05},
};
const auto& scores = GetAllStoredScores();
const auto& incognito_scores = GetAllStoredScores(incognito_service);
EXPECT_EQ(kExpectedResults.size(), scores.size());
EXPECT_EQ(kExpectedResults.size(), incognito_scores.size());
for (size_t i = 0; i < scores.size(); ++i) {
EXPECT_EQ(kExpectedResults[i].first, scores[i].origin());
EXPECT_EQ(kExpectedResults[i].second, scores[i].actual_score());
EXPECT_EQ(kExpectedResults[i].first, incognito_scores[i].origin());
EXPECT_EQ(kExpectedResults[i].second, incognito_scores[i].actual_score());
}
}
incognito_service->RecordVisit(kOrigin1);
RecordPlaybackForService(incognito_service, kOrigin2);
// Score shouldn't have changed in regular profile.
{
std::vector<std::pair<url::Origin, double>> kExpectedResults = {
{kOrigin2, 0.0},
{kOrigin1, 0.05},
};
const auto& scores = GetAllStoredScores();
EXPECT_EQ(kExpectedResults.size(), scores.size());
for (size_t i = 0; i < scores.size(); ++i) {
EXPECT_EQ(kExpectedResults[i].first, scores[i].origin());
EXPECT_EQ(kExpectedResults[i].second, scores[i].actual_score());
}
}
// Incognito scores should have the same number of entries but have new
// values.
{
std::vector<std::pair<url::Origin, double>> kExpectedResults = {
{kOrigin2, 0.05},
{kOrigin1, 1.0 / 21.0},
};
const auto& scores = GetAllStoredScores(incognito_service);
EXPECT_EQ(kExpectedResults.size(), scores.size());
for (size_t i = 0; i < scores.size(); ++i) {
EXPECT_EQ(kExpectedResults[i].first, scores[i].origin());
EXPECT_EQ(kExpectedResults[i].second, scores[i].actual_score());
}
}
}
TEST_P(MediaEngagementServiceTest, CleanupOriginsOnHistoryDeletion) {
url::Origin origin1 = url::Origin::Create(GURL("https://www.google.com/"));
url::Origin origin2 = url::Origin::Create(GURL("https://drive.google.com/"));
url::Origin origin3 = url::Origin::Create(GURL("https://deleted.com/"));
url::Origin origin4 = url::Origin::Create(GURL("https://notdeleted.com"));
GURL url1a = GURL("https://www.google.com/search?q=asdf");
GURL url1b = GURL("https://www.google.com/maps/search?q=asdf");
GURL url3a = GURL("https://deleted.com/test");
// origin1 will have a score that is high enough to not return zero
// and we will ensure it has the same score. origin2 will have a score
// that is zero and will remain zero. origin3 will have a score
// and will be cleared. origin4 will have a normal score.
SetScores(origin1, MediaEngagementScore::GetScoreMinVisits() + 2, 14);
SetScores(origin2, 2, 1);
SetScores(origin3, 2, 1);
SetScores(origin4, MediaEngagementScore::GetScoreMinVisits(), 10);
base::Time today = GetReferenceTime();
base::Time yesterday = GetReferenceTime() - base::TimeDelta::FromDays(1);
base::Time yesterday_afternoon = GetReferenceTime() -
base::TimeDelta::FromDays(1) +
base::TimeDelta::FromHours(4);
base::Time yesterday_week = GetReferenceTime() - base::TimeDelta::FromDays(8);
SetNow(today);
history::HistoryService* history = HistoryServiceFactory::GetForProfile(
profile(), ServiceAccessType::IMPLICIT_ACCESS);
history->AddPage(origin1.GetURL(), yesterday_afternoon,
history::SOURCE_BROWSED);
history->AddPage(url1a, yesterday_afternoon, history::SOURCE_BROWSED);
history->AddPage(url1b, yesterday_week, history::SOURCE_BROWSED);
history->AddPage(origin2.GetURL(), yesterday_afternoon,
history::SOURCE_BROWSED);
history->AddPage(origin3.GetURL(), yesterday_week, history::SOURCE_BROWSED);
history->AddPage(url3a, yesterday_afternoon, history::SOURCE_BROWSED);
// Check that the scores are valid at the beginning.
ExpectScores(origin1, 7.0 / 11.0,
MediaEngagementScore::GetScoreMinVisits() + 2, 14, TimeNotSet());
EXPECT_EQ(14.0 / 22.0, GetActualScore(origin1));
ExpectScores(origin2, 0.05, 2, 1, TimeNotSet());
EXPECT_EQ(1 / 20.0, GetActualScore(origin2));
ExpectScores(origin3, 0.05, 2, 1, TimeNotSet());
EXPECT_EQ(1 / 20.0, GetActualScore(origin3));
ExpectScores(origin4, 0.5, MediaEngagementScore::GetScoreMinVisits(), 10,
TimeNotSet());
EXPECT_EQ(0.5, GetActualScore(origin4));
{
MediaEngagementChangeWaiter waiter(profile());
base::CancelableTaskTracker task_tracker;
// Expire origin1, url1a, origin2, and url3a's most recent visit.
history->ExpireHistoryBetween(std::set<GURL>(), yesterday, today,
/*user_initiated*/ true, base::DoNothing(),
&task_tracker);
waiter.Wait();
// origin1 should have a score that is not zero and is the same as the old
// score (sometimes it may not match exactly due to rounding). origin2
// should have a score that is zero but it's visits and playbacks should
// have decreased. origin3 should have had a decrease in the number of
// visits. origin4 should have the old score.
ExpectScores(origin1, 0.6, MediaEngagementScore::GetScoreMinVisits(), 12,
TimeNotSet());
EXPECT_EQ(12.0 / 20.0, GetActualScore(origin1));
ExpectScores(origin2, 0.0, 1, 0, TimeNotSet());
EXPECT_EQ(0, GetActualScore(origin2));
ExpectScores(origin3, 0.0, 1, 0, TimeNotSet());
ExpectScores(origin4, 0.5, MediaEngagementScore::GetScoreMinVisits(), 10,
TimeNotSet());
}
{
MediaEngagementChangeWaiter waiter(profile());
// Expire url1b.
std::vector<history::ExpireHistoryArgs> expire_list;
history::ExpireHistoryArgs args;
args.urls.insert(url1b);
args.SetTimeRangeForOneDay(yesterday_week);
expire_list.push_back(args);
base::CancelableTaskTracker task_tracker;
history->ExpireHistory(expire_list, base::DoNothing(), &task_tracker);
waiter.Wait();
// origin1's score should have changed but the rest should remain the same.
ExpectScores(origin1, 0.55, MediaEngagementScore::GetScoreMinVisits() - 1,
11, TimeNotSet());
ExpectScores(origin2, 0.0, 1, 0, TimeNotSet());
ExpectScores(origin3, 0.0, 1, 0, TimeNotSet());
ExpectScores(origin4, 0.5, MediaEngagementScore::GetScoreMinVisits(), 10,
TimeNotSet());
}
{
MediaEngagementChangeWaiter waiter(profile());
// Expire origin3.
std::vector<history::ExpireHistoryArgs> expire_list;
history::ExpireHistoryArgs args;
args.urls.insert(origin3.GetURL());
args.SetTimeRangeForOneDay(yesterday_week);
expire_list.push_back(args);
base::CancelableTaskTracker task_tracker;
history->ExpireHistory(expire_list, base::DoNothing(), &task_tracker);
waiter.Wait();
// origin3's score should be removed but the rest should remain the same.
std::map<url::Origin, double> scores = GetScoreMapForTesting();
EXPECT_TRUE(scores.find(origin3) == scores.end());
ExpectScores(origin1, 0.55, MediaEngagementScore::GetScoreMinVisits() - 1,
11, TimeNotSet());
ExpectScores(origin2, 0.0, 1, 0, TimeNotSet());
ExpectScores(origin3, 0.0, 0, 0, TimeNotSet());
ExpectScores(origin4, 0.5, MediaEngagementScore::GetScoreMinVisits(), 10,
TimeNotSet());
}
}
// The test is flaky: crbug.com/1042417.
#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID)
#define MAYBE_CleanUpDatabaseWhenHistoryIsExpired \
DISABLED_CleanUpDatabaseWhenHistoryIsExpired
#else
#define MAYBE_CleanUpDatabaseWhenHistoryIsExpired \
CleanUpDatabaseWhenHistoryIsExpired
#endif
TEST_P(MediaEngagementServiceTest, MAYBE_CleanUpDatabaseWhenHistoryIsExpired) {
// |origin1| will have history that is before the expiry threshold and should
// not be deleted. |origin2| will have history either side of the threshold
// and should also not be deleted. |origin3| will have history before the
// threshold and should be deleted.
url::Origin origin1 = url::Origin::Create(GURL("https://www.google.com"));
url::Origin origin2 = url::Origin::Create(GURL("https://drive.google.com"));
url::Origin origin3 = url::Origin::Create(GURL("https://deleted.com"));
// Populate test MEI data.
SetScores(origin1, 20, 20);
SetScores(origin2, 30, 30);
SetScores(origin3, 40, 40);
base::Time today = base::Time::Now();
base::Time before_threshold = today - kHistoryExpirationThreshold;
SetNow(today);
// Populate test history records.
history::HistoryService* history = HistoryServiceFactory::GetForProfile(
profile(), ServiceAccessType::IMPLICIT_ACCESS);
history->AddPage(origin1.GetURL(), today, history::SOURCE_BROWSED);
history->AddPage(origin2.GetURL(), today, history::SOURCE_BROWSED);
history->AddPage(origin2.GetURL(), before_threshold, history::SOURCE_BROWSED);
history->AddPage(origin3.GetURL(), before_threshold, history::SOURCE_BROWSED);
// Expire history older than |threshold|.
MediaEngagementChangeWaiter waiter(profile());
RestartHistoryService(mock_time_task_runner_);
// From this point profile() is using a new HistoryService that runs on
// mock time.
// Now, fast forward time to ensure that the expiration job is completed. This
// will start by triggering the backend initialization. 30 seconds is the
// value of kExpirationDelaySec.
mock_time_task_runner_->FastForwardBy(base::TimeDelta::FromSeconds(30));
waiter.Wait();
// Check the scores for the test origins.
ExpectScores(origin1, 1.0, 20, 20, TimeNotSet());
ExpectScores(origin2, 1.0, 30, 30, TimeNotSet());
ExpectScores(origin3, 0, 0, 0, TimeNotSet());
}
TEST_P(MediaEngagementServiceTest, CleanUpDatabaseWhenHistoryIsDeleted) {
url::Origin origin1 = url::Origin::Create(GURL("https://www.google.com/"));
url::Origin origin2 = url::Origin::Create(GURL("https://drive.google.com/"));
url::Origin origin3 = url::Origin::Create(GURL("https://deleted.com/"));
url::Origin origin4 = url::Origin::Create(GURL("https://notdeleted.com"));
GURL url1a = GURL("https://www.google.com/search?q=asdf");
GURL url1b = GURL("https://www.google.com/maps/search?q=asdf");
GURL url3a = GURL("https://deleted.com/test");
// origin1 will have a score that is high enough to not return zero
// and we will ensure it has the same score. origin2 will have a score
// that is zero and will remain zero. origin3 will have a score
// and will be cleared. origin4 will have a normal score.
SetScores(origin1, MediaEngagementScore::GetScoreMinVisits() + 2, 14);
SetScores(origin2, 2, 1);
SetScores(origin3, 2, 1);
SetScores(origin4, MediaEngagementScore::GetScoreMinVisits(), 10);
base::Time today = GetReferenceTime();
base::Time yesterday_afternoon = GetReferenceTime() -
base::TimeDelta::FromDays(1) +
base::TimeDelta::FromHours(4);
base::Time yesterday_week = GetReferenceTime() - base::TimeDelta::FromDays(8);
SetNow(today);
history::HistoryService* history = HistoryServiceFactory::GetForProfile(
profile(), ServiceAccessType::IMPLICIT_ACCESS);
history->AddPage(origin1.GetURL(), yesterday_afternoon,
history::SOURCE_BROWSED);
history->AddPage(url1a, yesterday_afternoon, history::SOURCE_BROWSED);
history->AddPage(url1b, yesterday_week, history::SOURCE_BROWSED);
history->AddPage(origin2.GetURL(), yesterday_afternoon,
history::SOURCE_BROWSED);
history->AddPage(origin3.GetURL(), yesterday_week, history::SOURCE_BROWSED);
history->AddPage(url3a, yesterday_afternoon, history::SOURCE_BROWSED);
// Check that the scores are valid at the beginning.
ExpectScores(origin1, 7.0 / 11.0,
MediaEngagementScore::GetScoreMinVisits() + 2, 14, TimeNotSet());
EXPECT_EQ(14.0 / 22.0, GetActualScore(origin1));
ExpectScores(origin2, 0.05, 2, 1, TimeNotSet());
EXPECT_EQ(1 / 20.0, GetActualScore(origin2));
ExpectScores(origin3, 0.05, 2, 1, TimeNotSet());
EXPECT_EQ(1 / 20.0, GetActualScore(origin3));
ExpectScores(origin4, 0.5, MediaEngagementScore::GetScoreMinVisits(), 10,
TimeNotSet());
EXPECT_EQ(0.5, GetActualScore(origin4));
{
base::RunLoop run_loop;
base::CancelableTaskTracker task_tracker;
// Clear all history.
history->ExpireHistoryBetween(std::set<GURL>(), base::Time(), base::Time(),
/*user_initiated*/ true,
run_loop.QuitClosure(), &task_tracker);
run_loop.Run();
// origin1 should have a score that is not zero and is the same as the old
// score (sometimes it may not match exactly due to rounding). origin2
// should have a score that is zero but it's visits and playbacks should
// have decreased. origin3 should have had a decrease in the number of
// visits. origin4 should have the old score.
ExpectScores(origin1, 0.0, 0, 0, TimeNotSet());
EXPECT_EQ(0, GetActualScore(origin1));
ExpectScores(origin2, 0.0, 0, 0, TimeNotSet());
EXPECT_EQ(0, GetActualScore(origin2));
ExpectScores(origin3, 0.0, 0, 0, TimeNotSet());
ExpectScores(origin4, 0.0, 0, 0, TimeNotSet());
}
}
TEST_P(MediaEngagementServiceTest, HistoryExpirationIsNoOp) {
url::Origin origin1 = url::Origin::Create(GURL("https://www.google.com/"));
url::Origin origin2 = url::Origin::Create(GURL("https://drive.google.com/"));
url::Origin origin3 = url::Origin::Create(GURL("https://deleted.com/"));
url::Origin origin4 = url::Origin::Create(GURL("https://notdeleted.com"));
GURL url1a = GURL("https://www.google.com/search?q=asdf");
GURL url1b = GURL("https://www.google.com/maps/search?q=asdf");
GURL url3a = GURL("https://deleted.com/test");
SetScores(origin1, MediaEngagementScore::GetScoreMinVisits() + 2, 14);
SetScores(origin2, 2, 1);
SetScores(origin3, 2, 1);
SetScores(origin4, MediaEngagementScore::GetScoreMinVisits(), 10);
ExpectScores(origin1, 7.0 / 11.0,
MediaEngagementScore::GetScoreMinVisits() + 2, 14, TimeNotSet());
EXPECT_EQ(14.0 / 22.0, GetActualScore(origin1));
ExpectScores(origin2, 0.05, 2, 1, TimeNotSet());
EXPECT_EQ(1 / 20.0, GetActualScore(origin2));
ExpectScores(origin3, 0.05, 2, 1, TimeNotSet());
EXPECT_EQ(1 / 20.0, GetActualScore(origin3));
ExpectScores(origin4, 0.5, MediaEngagementScore::GetScoreMinVisits(), 10,
TimeNotSet());
EXPECT_EQ(0.5, GetActualScore(origin4));
{
history::HistoryService* history = HistoryServiceFactory::GetForProfile(
profile(), ServiceAccessType::IMPLICIT_ACCESS);
service()->OnURLsDeleted(
history, history::DeletionInfo(history::DeletionTimeRange::Invalid(),
true, history::URLRows(),
std::set<GURL>(), base::nullopt));
// Same as above, nothing should have changed.
ExpectScores(origin1, 7.0 / 11.0,
MediaEngagementScore::GetScoreMinVisits() + 2, 14,
TimeNotSet());
EXPECT_EQ(14.0 / 22.0, GetActualScore(origin1));
ExpectScores(origin2, 0.05, 2, 1, TimeNotSet());
EXPECT_EQ(1 / 20.0, GetActualScore(origin2));
ExpectScores(origin3, 0.05, 2, 1, TimeNotSet());
EXPECT_EQ(1 / 20.0, GetActualScore(origin3));
ExpectScores(origin4, 0.5, MediaEngagementScore::GetScoreMinVisits(), 10,
TimeNotSet());
EXPECT_EQ(0.5, GetActualScore(origin4));
}
}
TEST_P(MediaEngagementServiceTest,
CleanupDataOnSiteDataCleanup_OutsideBoundary) {
url::Origin origin = url::Origin::Create(GURL("https://www.google.com"));
base::Time today = GetReferenceTime();
SetNow(today);
SetScores(origin, 1, 1);
SetLastMediaPlaybackTime(origin, today);
ClearDataBetweenTime(today - base::TimeDelta::FromDays(2),
today - base::TimeDelta::FromDays(1));
ExpectScores(origin, 0.05, 1, 1, today);
}
TEST_P(MediaEngagementServiceTest,
CleanupDataOnSiteDataCleanup_WithinBoundary) {
url::Origin origin1 = url::Origin::Create(GURL("https://www.google.com"));
url::Origin origin2 = url::Origin::Create(GURL("https://www.google.co.uk"));
base::Time today = GetReferenceTime();
base::Time yesterday = today - base::TimeDelta::FromDays(1);
base::Time two_days_ago = today - base::TimeDelta::FromDays(2);
SetNow(today);
SetScores(origin1, 1, 1);
SetScores(origin2, 1, 1);
SetLastMediaPlaybackTime(origin1, yesterday);
SetLastMediaPlaybackTime(origin2, two_days_ago);
ClearDataBetweenTime(two_days_ago, yesterday);
ExpectScores(origin1, 0, 0, 0, TimeNotSet());
ExpectScores(origin2, 0, 0, 0, TimeNotSet());
}
TEST_P(MediaEngagementServiceTest, CleanupDataOnSiteDataCleanup_NoTimeSet) {
url::Origin origin = url::Origin::Create(GURL("https://www.google.com"));
base::Time today = GetReferenceTime();
SetNow(GetReferenceTime());
SetScores(origin, 1, 0);
ClearDataBetweenTime(today - base::TimeDelta::FromDays(2),
today - base::TimeDelta::FromDays(1));
ExpectScores(origin, 0.0, 1, 0, TimeNotSet());
}
TEST_P(MediaEngagementServiceTest, CleanupDataOnSiteDataCleanup_All) {
url::Origin origin1 = url::Origin::Create(GURL("https://www.google.com"));
url::Origin origin2 = url::Origin::Create(GURL("https://www.google.co.uk"));
base::Time today = GetReferenceTime();
base::Time yesterday = today - base::TimeDelta::FromDays(1);
base::Time two_days_ago = today - base::TimeDelta::FromDays(2);
SetNow(today);
SetScores(origin1, 1, 1);
SetScores(origin2, 1, 1);
SetLastMediaPlaybackTime(origin1, yesterday);
SetLastMediaPlaybackTime(origin2, two_days_ago);
ClearDataBetweenTime(base::Time(), base::Time::Max());
ExpectScores(origin1, 0, 0, 0, TimeNotSet());
ExpectScores(origin2, 0, 0, 0, TimeNotSet());
}
TEST_P(MediaEngagementServiceTest, HasHighEngagement) {
url::Origin origin1 = url::Origin::Create(GURL("https://www.google.com"));
url::Origin origin2 = url::Origin::Create(GURL("https://www.google.co.uk"));
url::Origin origin3 = url::Origin::Create(GURL("https://www.example.com"));
SetScores(origin1, 20, 15);
SetScores(origin2, 20, 4);
EXPECT_TRUE(HasHighEngagement(origin1));
EXPECT_FALSE(HasHighEngagement(origin2));
EXPECT_FALSE(HasHighEngagement(origin3));
}
TEST_P(MediaEngagementServiceTest, SchemaVersion_Changed) {
url::Origin origin = url::Origin::Create(GURL("https://www.google.com"));
SetScores(origin, 1, 2);
SetSchemaVersion(0);
std::unique_ptr<MediaEngagementService> new_service =
base::WrapUnique<MediaEngagementService>(
StartNewMediaEngagementService());
ExpectScores(new_service.get(), origin, 0.0, 0, 0, TimeNotSet());
new_service->Shutdown();
}
TEST_P(MediaEngagementServiceTest, SchemaVersion_Same) {
url::Origin origin = url::Origin::Create(GURL("https://www.google.com"));
SetScores(origin, 1, 2);
std::unique_ptr<MediaEngagementService> new_service =
base::WrapUnique<MediaEngagementService>(
StartNewMediaEngagementService());
ExpectScores(new_service.get(), origin, 0.1, 1, 2, TimeNotSet());
new_service->Shutdown();
}
INSTANTIATE_TEST_SUITE_P(All, MediaEngagementServiceTest, ::testing::Bool());
class MediaEngagementServiceEnabledTest
: public ChromeRenderViewHostTestHarness {};
TEST_F(MediaEngagementServiceEnabledTest, IsEnabled) {
#if defined(OS_ANDROID)
// Make sure these flags are disabled on Android
EXPECT_FALSE(base::FeatureList::IsEnabled(
media::kMediaEngagementBypassAutoplayPolicies));
EXPECT_FALSE(
base::FeatureList::IsEnabled(media::kPreloadMediaEngagementData));
#else
EXPECT_TRUE(base::FeatureList::IsEnabled(
media::kMediaEngagementBypassAutoplayPolicies));
EXPECT_TRUE(base::FeatureList::IsEnabled(media::kPreloadMediaEngagementData));
#endif
}