// Copyright 2015 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 "base/command_line.h" #include "base/macros.h" #include "base/run_loop.h" #include "base/test/histogram_tester.h" #include "base/test/simple_test_clock.h" #include "base/values.h" #include "chrome/browser/content_settings/host_content_settings_map_factory.h" #include "chrome/browser/engagement/site_engagement_helper.h" #include "chrome/browser/engagement/site_engagement_metrics.h" #include "chrome/browser/engagement/site_engagement_service.h" #include "chrome/browser/engagement/site_engagement_service_factory.h" #include "chrome/browser/history/history_service_factory.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/common/chrome_switches.h" #include "chrome/test/base/browser_with_test_window_test.h" #include "components/content_settings/core/browser/content_settings_observer.h" #include "components/content_settings/core/browser/host_content_settings_map.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 "content/public/browser/page_navigator.h" #include "testing/gtest/include/gtest/gtest.h" namespace { base::FilePath g_temp_history_dir; const int kLessAccumulationsThanNeededToMaxDailyEngagement = 2; const int kMoreAccumulationsThanNeededToMaxDailyEngagement = 40; const int kMoreAccumulationsThanNeededToMaxTotalEngagement = 200; const int kLessDaysThanNeededToMaxTotalEngagement = 4; const int kMoreDaysThanNeededToMaxTotalEngagement = 40; const int kLessPeriodsThanNeededToDecayMaxScore = 2; const int kMorePeriodsThanNeededToDecayMaxScore = 40; // Waits until a change is observed in site engagement content settings. class SiteEngagementChangeWaiter : public content_settings::Observer { public: explicit SiteEngagementChangeWaiter(Profile* profile) : profile_(profile) { HostContentSettingsMapFactory::GetForProfile(profile)->AddObserver(this); } ~SiteEngagementChangeWaiter() override { HostContentSettingsMapFactory::GetForProfile(profile_) ->RemoveObserver(this); } // Overridden from content_settings::Observer: void OnContentSettingChanged(const ContentSettingsPattern& primary_pattern, const ContentSettingsPattern& secondary_pattern, ContentSettingsType content_type, std::string resource_identifier) override { if (content_type == CONTENT_SETTINGS_TYPE_SITE_ENGAGEMENT) Proceed(); } void Wait() { run_loop_.Run(); } private: void Proceed() { run_loop_.Quit(); } Profile* profile_; base::RunLoop run_loop_; DISALLOW_COPY_AND_ASSIGN(SiteEngagementChangeWaiter); }; 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; return base::Time::FromLocalExploded(exploded_reference_time); } scoped_ptr BuildTestHistoryService( content::BrowserContext* context) { scoped_ptr service(new history::HistoryService()); service->Init(std::string(), history::TestHistoryDatabaseParamsForPath(g_temp_history_dir)); return service.Pass(); } } // namespace class SiteEngagementScoreTest : public testing::Test { public: SiteEngagementScoreTest() : score_(&test_clock_) {} protected: void VerifyScore(const SiteEngagementScore& score, double expected_raw_score, double expected_points_added_today, base::Time expected_last_engagement_time) { EXPECT_EQ(expected_raw_score, score.raw_score_); EXPECT_EQ(expected_points_added_today, score.points_added_today_); EXPECT_EQ(expected_last_engagement_time, score.last_engagement_time_); } void UpdateScore(SiteEngagementScore* score, double raw_score, double points_added_today, base::Time last_engagement_time) { score->raw_score_ = raw_score; score->points_added_today_ = points_added_today; score->last_engagement_time_ = last_engagement_time; } void TestScoreInitializesAndUpdates( base::DictionaryValue* score_dict, double expected_raw_score, double expected_points_added_today, base::Time expected_last_engagement_time) { SiteEngagementScore initial_score(&test_clock_, *score_dict); VerifyScore(initial_score, expected_raw_score, expected_points_added_today, expected_last_engagement_time); // Updating the score dict should return false, as the score shouldn't // have changed at this point. EXPECT_FALSE(initial_score.UpdateScoreDict(score_dict)); // Update the score to new values and verify it updates the score dict // correctly. base::Time different_day = GetReferenceTime() + base::TimeDelta::FromDays(1); UpdateScore(&initial_score, 5, 10, different_day); EXPECT_TRUE(initial_score.UpdateScoreDict(score_dict)); SiteEngagementScore updated_score(&test_clock_, *score_dict); VerifyScore(updated_score, 5, 10, different_day); } base::SimpleTestClock test_clock_; SiteEngagementScore score_; }; // Accumulate score many times on the same day. Ensure each time the score goes // up, but not more than the maximum per day. TEST_F(SiteEngagementScoreTest, AccumulateOnSameDay) { base::Time reference_time = GetReferenceTime(); test_clock_.SetNow(reference_time); for (int i = 0; i < kMoreAccumulationsThanNeededToMaxDailyEngagement; ++i) { score_.AddPoints(SiteEngagementScore::GetNavigationPoints()); EXPECT_EQ(std::min(SiteEngagementScore::GetMaxPointsPerDay(), (i + 1) * SiteEngagementScore::GetNavigationPoints()), score_.Score()); } EXPECT_EQ(SiteEngagementScore::GetMaxPointsPerDay(), score_.Score()); } // Accumulate on the first day to max that day's engagement, then accumulate on // a different day. TEST_F(SiteEngagementScoreTest, AccumulateOnTwoDays) { base::Time reference_time = GetReferenceTime(); base::Time later_date = reference_time + base::TimeDelta::FromDays(2); test_clock_.SetNow(reference_time); for (int i = 0; i < kMoreAccumulationsThanNeededToMaxDailyEngagement; ++i) score_.AddPoints(SiteEngagementScore::GetNavigationPoints()); EXPECT_EQ(SiteEngagementScore::GetMaxPointsPerDay(), score_.Score()); test_clock_.SetNow(later_date); for (int i = 0; i < kMoreAccumulationsThanNeededToMaxDailyEngagement; ++i) { score_.AddPoints(SiteEngagementScore::GetNavigationPoints()); double day_score = std::min(SiteEngagementScore::GetMaxPointsPerDay(), (i + 1) * SiteEngagementScore::GetNavigationPoints()); EXPECT_EQ(day_score + SiteEngagementScore::GetMaxPointsPerDay(), score_.Score()); } EXPECT_EQ(2 * SiteEngagementScore::GetMaxPointsPerDay(), score_.Score()); } // Accumulate score on many consecutive days and ensure the score doesn't exceed // the maximum allowed. TEST_F(SiteEngagementScoreTest, AccumulateALotOnManyDays) { base::Time current_day = GetReferenceTime(); for (int i = 0; i < kMoreDaysThanNeededToMaxTotalEngagement; ++i) { current_day += base::TimeDelta::FromDays(1); test_clock_.SetNow(current_day); for (int j = 0; j < kMoreAccumulationsThanNeededToMaxDailyEngagement; ++j) score_.AddPoints(SiteEngagementScore::GetNavigationPoints()); EXPECT_EQ(std::min(SiteEngagementScore::kMaxPoints, (i + 1) * SiteEngagementScore::GetMaxPointsPerDay()), score_.Score()); } EXPECT_EQ(SiteEngagementScore::kMaxPoints, score_.Score()); } // Accumulate a little on many consecutive days and ensure the score doesn't // exceed the maximum allowed. TEST_F(SiteEngagementScoreTest, AccumulateALittleOnManyDays) { base::Time current_day = GetReferenceTime(); for (int i = 0; i < kMoreAccumulationsThanNeededToMaxTotalEngagement; ++i) { current_day += base::TimeDelta::FromDays(1); test_clock_.SetNow(current_day); for (int j = 0; j < kLessAccumulationsThanNeededToMaxDailyEngagement; ++j) score_.AddPoints(SiteEngagementScore::GetNavigationPoints()); EXPECT_EQ( std::min(SiteEngagementScore::kMaxPoints, (i + 1) * kLessAccumulationsThanNeededToMaxDailyEngagement * SiteEngagementScore::GetNavigationPoints()), score_.Score()); } EXPECT_EQ(SiteEngagementScore::kMaxPoints, score_.Score()); } // Accumulate a bit, then check the score decays properly for a range of times. TEST_F(SiteEngagementScoreTest, ScoresDecayOverTime) { base::Time current_day = GetReferenceTime(); // First max the score. for (int i = 0; i < kMoreDaysThanNeededToMaxTotalEngagement; ++i) { current_day += base::TimeDelta::FromDays(1); test_clock_.SetNow(current_day); for (int j = 0; j < kMoreAccumulationsThanNeededToMaxDailyEngagement; ++j) score_.AddPoints(SiteEngagementScore::GetNavigationPoints()); } EXPECT_EQ(SiteEngagementScore::kMaxPoints, score_.Score()); // The score should not have decayed before the first decay period has // elapsed. test_clock_.SetNow(current_day + base::TimeDelta::FromDays( SiteEngagementScore::GetDecayPeriodInDays() - 1)); EXPECT_EQ(SiteEngagementScore::kMaxPoints, score_.Score()); // The score should have decayed by one chunk after one decay period has // elapsed. test_clock_.SetNow( current_day + base::TimeDelta::FromDays(SiteEngagementScore::GetDecayPeriodInDays())); EXPECT_EQ( SiteEngagementScore::kMaxPoints - SiteEngagementScore::GetDecayPoints(), score_.Score()); // The score should have decayed by the right number of chunks after a few // decay periods have elapsed. test_clock_.SetNow( current_day + base::TimeDelta::FromDays(kLessPeriodsThanNeededToDecayMaxScore * SiteEngagementScore::GetDecayPeriodInDays())); EXPECT_EQ(SiteEngagementScore::kMaxPoints - kLessPeriodsThanNeededToDecayMaxScore * SiteEngagementScore::GetDecayPoints(), score_.Score()); // The score should not decay below zero. test_clock_.SetNow( current_day + base::TimeDelta::FromDays(kMorePeriodsThanNeededToDecayMaxScore * SiteEngagementScore::GetDecayPeriodInDays())); EXPECT_EQ(0, score_.Score()); } // Test that any expected decays are applied before adding points. TEST_F(SiteEngagementScoreTest, DecaysAppliedBeforeAdd) { base::Time current_day = GetReferenceTime(); // Get the score up to something that can handle a bit of decay before for (int i = 0; i < kLessDaysThanNeededToMaxTotalEngagement; ++i) { current_day += base::TimeDelta::FromDays(1); test_clock_.SetNow(current_day); for (int j = 0; j < kMoreAccumulationsThanNeededToMaxDailyEngagement; ++j) score_.AddPoints(SiteEngagementScore::GetNavigationPoints()); } double initial_score = kLessDaysThanNeededToMaxTotalEngagement * SiteEngagementScore::GetMaxPointsPerDay(); EXPECT_EQ(initial_score, score_.Score()); // Go forward a few decay periods. test_clock_.SetNow( current_day + base::TimeDelta::FromDays(kLessPeriodsThanNeededToDecayMaxScore * SiteEngagementScore::GetDecayPeriodInDays())); double decayed_score = initial_score - kLessPeriodsThanNeededToDecayMaxScore * SiteEngagementScore::GetDecayPoints(); EXPECT_EQ(decayed_score, score_.Score()); // Now add some points. score_.AddPoints(SiteEngagementScore::GetNavigationPoints()); EXPECT_EQ(decayed_score + SiteEngagementScore::GetNavigationPoints(), score_.Score()); } // Test that going back in time is handled properly. TEST_F(SiteEngagementScoreTest, GoBackInTime) { base::Time current_day = GetReferenceTime(); test_clock_.SetNow(current_day); for (int i = 0; i < kMoreAccumulationsThanNeededToMaxDailyEngagement; ++i) score_.AddPoints(SiteEngagementScore::GetNavigationPoints()); EXPECT_EQ(SiteEngagementScore::GetMaxPointsPerDay(), score_.Score()); // Adding to the score on an earlier date should be treated like another day, // and should not cause any decay. test_clock_.SetNow(current_day - base::TimeDelta::FromDays( kMorePeriodsThanNeededToDecayMaxScore * SiteEngagementScore::GetDecayPoints())); for (int i = 0; i < kMoreAccumulationsThanNeededToMaxDailyEngagement; ++i) { score_.AddPoints(SiteEngagementScore::GetNavigationPoints()); double day_score = std::min(SiteEngagementScore::GetMaxPointsPerDay(), (i + 1) * SiteEngagementScore::GetNavigationPoints()); EXPECT_EQ(day_score + SiteEngagementScore::GetMaxPointsPerDay(), score_.Score()); } EXPECT_EQ(2 * SiteEngagementScore::GetMaxPointsPerDay(), score_.Score()); } // Test that scores are read / written correctly from / to empty score // dictionaries. TEST_F(SiteEngagementScoreTest, EmptyDictionary) { base::DictionaryValue dict; TestScoreInitializesAndUpdates(&dict, 0, 0, base::Time()); } // Test that scores are read / written correctly from / to partially empty // score dictionaries. TEST_F(SiteEngagementScoreTest, PartiallyEmptyDictionary) { base::DictionaryValue dict; dict.SetDouble(SiteEngagementScore::kPointsAddedTodayKey, 2); TestScoreInitializesAndUpdates(&dict, 0, 2, base::Time()); } // Test that scores are read / written correctly from / to populated score // dictionaries. TEST_F(SiteEngagementScoreTest, PopulatedDictionary) { base::DictionaryValue dict; dict.SetDouble(SiteEngagementScore::kRawScoreKey, 1); dict.SetDouble(SiteEngagementScore::kPointsAddedTodayKey, 2); dict.SetDouble(SiteEngagementScore::kLastEngagementTimeKey, GetReferenceTime().ToInternalValue()); TestScoreInitializesAndUpdates(&dict, 1, 2, GetReferenceTime()); } class SiteEngagementServiceTest : public BrowserWithTestWindowTest { public: SiteEngagementServiceTest() {} void SetUp() override { BrowserWithTestWindowTest::SetUp(); ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); g_temp_history_dir = temp_dir_.path(); HistoryServiceFactory::GetInstance()->SetTestingFactory( profile(), &BuildTestHistoryService); base::CommandLine::ForCurrentProcess()->AppendSwitch( switches::kEnableSiteEngagementService); } void NavigateWithTransitionAndExpectHigherScore( SiteEngagementService* service, GURL& url, ui::PageTransition transition) { double prev_score = service->GetScore(url); content::NavigationController* controller = &browser()->tab_strip_model()->GetActiveWebContents()->GetController(); browser()->OpenURL(content::OpenURLParams(url, content::Referrer(), CURRENT_TAB, transition, false)); CommitPendingLoad(controller); EXPECT_LT(prev_score, service->GetScore(url)); } void NavigateWithTransitionAndExpectEqualScore( SiteEngagementService* service, GURL& url, ui::PageTransition transition) { double prev_score = service->GetScore(url); content::NavigationController* controller = &browser()->tab_strip_model()->GetActiveWebContents()->GetController(); browser()->OpenURL(content::OpenURLParams(url, content::Referrer(), CURRENT_TAB, transition, false)); CommitPendingLoad(controller); EXPECT_EQ(prev_score, service->GetScore(url)); } private: base::ScopedTempDir temp_dir_; }; TEST_F(SiteEngagementServiceTest, GetMedianEngagement) { SiteEngagementService* service = SiteEngagementServiceFactory::GetForProfile(profile()); ASSERT_TRUE(service); GURL url1("http://www.google.com/"); GURL url2("https://www.google.com/"); GURL url3("https://drive.google.com/"); GURL url4("https://maps.google.com/"); GURL url5("https://youtube.com/"); GURL url6("https://images.google.com/"); { // For zero total sites, the median is 0. std::map score_map = service->GetScoreMap(); EXPECT_TRUE(0 == score_map.size()); EXPECT_DOUBLE_EQ(0, service->GetMedianEngagement(score_map)); } { // For odd total sites, the median is the middle score. service->AddPoints(url1, 1); std::map score_map = service->GetScoreMap(); EXPECT_TRUE(1 == score_map.size()); EXPECT_DOUBLE_EQ(1, service->GetMedianEngagement(score_map)); } { // For even total sites, the median is the mean of the middle two scores. service->AddPoints(url2, 2); std::map score_map = service->GetScoreMap(); EXPECT_TRUE(2 == score_map.size()); EXPECT_DOUBLE_EQ(1.5, service->GetMedianEngagement(score_map)); } { service->AddPoints(url3, 1.4); std::map score_map = service->GetScoreMap(); EXPECT_TRUE(3 == score_map.size()); EXPECT_DOUBLE_EQ(1.4, service->GetMedianEngagement(score_map)); } { service->AddPoints(url4, 1.8); std::map score_map = service->GetScoreMap(); EXPECT_TRUE(4 == score_map.size()); EXPECT_DOUBLE_EQ(1.6, service->GetMedianEngagement(score_map)); } { service->AddPoints(url5, 2.5); std::map score_map = service->GetScoreMap(); EXPECT_TRUE(5 == score_map.size()); EXPECT_DOUBLE_EQ(1.8, service->GetMedianEngagement(score_map)); } { service->AddPoints(url6, 3); std::map score_map = service->GetScoreMap(); EXPECT_TRUE(6 == score_map.size()); EXPECT_DOUBLE_EQ(1.9, service->GetMedianEngagement(score_map)); } } // Tests that the Site Engagement service is hooked up properly to navigations // by performing two navigations and checking the engagement score increases // both times. TEST_F(SiteEngagementServiceTest, ScoreIncrementsOnPageRequest) { SiteEngagementService* service = SiteEngagementServiceFactory::GetForProfile(profile()); ASSERT_TRUE(service); GURL url("http://www.google.com/"); EXPECT_EQ(0, service->GetScore(url)); AddTab(browser(), GURL("about:blank")); NavigateWithTransitionAndExpectHigherScore(service, url, ui::PAGE_TRANSITION_TYPED); NavigateWithTransitionAndExpectHigherScore(service, url, ui::PAGE_TRANSITION_AUTO_BOOKMARK); } // Expect that site engagement scores for several sites are correctly // aggregated during navigation events. TEST_F(SiteEngagementServiceTest, GetTotalNavigationPoints) { SiteEngagementService* service = SiteEngagementServiceFactory::GetForProfile(profile()); ASSERT_TRUE(service); // The https and http versions of www.google.com should be separate. GURL url1("https://www.google.com/"); GURL url2("http://www.google.com/"); GURL url3("http://drive.google.com/"); EXPECT_EQ(0, service->GetScore(url1)); EXPECT_EQ(0, service->GetScore(url2)); EXPECT_EQ(0, service->GetScore(url3)); service->HandleNavigation(url1, ui::PAGE_TRANSITION_TYPED); EXPECT_EQ(0.5, service->GetScore(url1)); EXPECT_EQ(0.5, service->GetTotalEngagementPoints()); service->HandleNavigation(url2, ui::PAGE_TRANSITION_GENERATED); service->HandleNavigation(url2, ui::PAGE_TRANSITION_KEYWORD_GENERATED); EXPECT_EQ(1, service->GetScore(url2)); EXPECT_EQ(1.5, service->GetTotalEngagementPoints()); service->HandleNavigation(url2, ui::PAGE_TRANSITION_AUTO_BOOKMARK); EXPECT_EQ(1.5, service->GetScore(url2)); EXPECT_EQ(2, service->GetTotalEngagementPoints()); service->HandleNavigation(url3, ui::PAGE_TRANSITION_TYPED); EXPECT_EQ(0.5, service->GetScore(url3)); EXPECT_EQ(2.5, service->GetTotalEngagementPoints()); service->HandleNavigation(url1, ui::PAGE_TRANSITION_GENERATED); service->HandleNavigation(url1, ui::PAGE_TRANSITION_TYPED); EXPECT_EQ(1.5, service->GetScore(url1)); EXPECT_EQ(3.5, service->GetTotalEngagementPoints()); } TEST_F(SiteEngagementServiceTest, GetTotalUserInputPoints) { SiteEngagementService* service = SiteEngagementServiceFactory::GetForProfile(profile()); ASSERT_TRUE(service); // The https and http versions of www.google.com should be separate. GURL url1("https://www.google.com/"); GURL url2("http://www.google.com/"); GURL url3("http://drive.google.com/"); EXPECT_EQ(0, service->GetScore(url1)); EXPECT_EQ(0, service->GetScore(url2)); EXPECT_EQ(0, service->GetScore(url3)); service->HandleUserInput(url1, SiteEngagementMetrics::ENGAGEMENT_MOUSE); EXPECT_DOUBLE_EQ(0.05, service->GetScore(url1)); EXPECT_DOUBLE_EQ(0.05, service->GetTotalEngagementPoints()); service->HandleUserInput(url2, SiteEngagementMetrics::ENGAGEMENT_MOUSE); service->HandleUserInput(url2, SiteEngagementMetrics::ENGAGEMENT_KEYPRESS); EXPECT_DOUBLE_EQ(0.1, service->GetScore(url2)); EXPECT_DOUBLE_EQ(0.15, service->GetTotalEngagementPoints()); service->HandleUserInput(url3, SiteEngagementMetrics::ENGAGEMENT_KEYPRESS); EXPECT_DOUBLE_EQ(0.05, service->GetScore(url3)); EXPECT_DOUBLE_EQ(0.2, service->GetTotalEngagementPoints()); service->HandleUserInput(url1, SiteEngagementMetrics::ENGAGEMENT_KEYPRESS); service->HandleUserInput(url1, SiteEngagementMetrics::ENGAGEMENT_MOUSE); EXPECT_DOUBLE_EQ(0.15, service->GetScore(url1)); EXPECT_DOUBLE_EQ(0.3, service->GetTotalEngagementPoints()); service->HandleUserInput(url2, SiteEngagementMetrics::ENGAGEMENT_WHEEL); service->HandleUserInput(url3, SiteEngagementMetrics::ENGAGEMENT_TOUCH_GESTURE); EXPECT_DOUBLE_EQ(0.15, service->GetScore(url2)); EXPECT_DOUBLE_EQ(0.1, service->GetScore(url3)); EXPECT_DOUBLE_EQ(0.4, service->GetTotalEngagementPoints()); } TEST_F(SiteEngagementServiceTest, CheckHistograms) { base::SimpleTestClock* clock = new base::SimpleTestClock(); scoped_ptr service( new SiteEngagementService(profile(), make_scoped_ptr(clock))); base::Time current_day = GetReferenceTime(); clock->SetNow(current_day); base::HistogramTester histograms; // Histograms should start empty as the testing SiteEngagementService // constructor does not record metrics. histograms.ExpectTotalCount(SiteEngagementMetrics::kTotalEngagementHistogram, 0); histograms.ExpectTotalCount(SiteEngagementMetrics::kTotalOriginsHistogram, 0); histograms.ExpectTotalCount(SiteEngagementMetrics::kMeanEngagementHistogram, 0); histograms.ExpectTotalCount(SiteEngagementMetrics::kMedianEngagementHistogram, 0); histograms.ExpectTotalCount(SiteEngagementMetrics::kEngagementScoreHistogram, 0); histograms.ExpectTotalCount( SiteEngagementMetrics::kOriginsWithMaxEngagementHistogram, 0); histograms.ExpectTotalCount( SiteEngagementMetrics::kOriginsWithMaxDailyEngagementHistogram, 0); histograms.ExpectTotalCount( SiteEngagementMetrics::kPercentOriginsWithMaxEngagementHistogram, 0); histograms.ExpectTotalCount(SiteEngagementMetrics::kEngagementTypeHistogram, 0); // The https and http versions of www.google.com should be separate. GURL url1("https://www.google.com/"); GURL url2("http://www.google.com/"); GURL url3("http://drive.google.com/"); service->HandleNavigation(url1, ui::PAGE_TRANSITION_TYPED); service->HandleUserInput(url1, SiteEngagementMetrics::ENGAGEMENT_KEYPRESS); service->HandleUserInput(url1, SiteEngagementMetrics::ENGAGEMENT_MOUSE); service->HandleMediaPlaying(url2, true); histograms.ExpectTotalCount(SiteEngagementMetrics::kTotalEngagementHistogram, 1); histograms.ExpectUniqueSample(SiteEngagementMetrics::kTotalOriginsHistogram, 1, 1); histograms.ExpectTotalCount(SiteEngagementMetrics::kMeanEngagementHistogram, 1); histograms.ExpectTotalCount(SiteEngagementMetrics::kMedianEngagementHistogram, 1); // Recorded per origin. histograms.ExpectTotalCount(SiteEngagementMetrics::kEngagementScoreHistogram, 1); histograms.ExpectUniqueSample( SiteEngagementMetrics::kOriginsWithMaxEngagementHistogram, 0, 1); histograms.ExpectUniqueSample( SiteEngagementMetrics::kOriginsWithMaxDailyEngagementHistogram, 0, 1); histograms.ExpectUniqueSample( SiteEngagementMetrics::kPercentOriginsWithMaxEngagementHistogram, 0, 1); histograms.ExpectTotalCount(SiteEngagementMetrics::kEngagementTypeHistogram, 4); histograms.ExpectBucketCount(SiteEngagementMetrics::kEngagementTypeHistogram, SiteEngagementMetrics::ENGAGEMENT_NAVIGATION, 1); histograms.ExpectBucketCount(SiteEngagementMetrics::kEngagementTypeHistogram, SiteEngagementMetrics::ENGAGEMENT_KEYPRESS, 1); histograms.ExpectBucketCount(SiteEngagementMetrics::kEngagementTypeHistogram, SiteEngagementMetrics::ENGAGEMENT_MOUSE, 1); histograms.ExpectBucketCount(SiteEngagementMetrics::kEngagementTypeHistogram, SiteEngagementMetrics::ENGAGEMENT_MEDIA_HIDDEN, 1); clock->SetNow(GetReferenceTime() + base::TimeDelta::FromMinutes(59)); service->HandleNavigation(url2, ui::PAGE_TRANSITION_GENERATED); service->HandleNavigation(url2, ui::PAGE_TRANSITION_AUTO_BOOKMARK); histograms.ExpectTotalCount(SiteEngagementMetrics::kEngagementTypeHistogram, 6); histograms.ExpectBucketCount(SiteEngagementMetrics::kEngagementTypeHistogram, SiteEngagementMetrics::ENGAGEMENT_NAVIGATION, 3); histograms.ExpectBucketCount(SiteEngagementMetrics::kEngagementTypeHistogram, SiteEngagementMetrics::ENGAGEMENT_KEYPRESS, 1); histograms.ExpectBucketCount(SiteEngagementMetrics::kEngagementTypeHistogram, SiteEngagementMetrics::ENGAGEMENT_MOUSE, 1); histograms.ExpectBucketCount(SiteEngagementMetrics::kEngagementTypeHistogram, SiteEngagementMetrics::ENGAGEMENT_MEDIA_HIDDEN, 1); clock->SetNow(GetReferenceTime() + base::TimeDelta::FromMinutes(60)); service->HandleNavigation(url3, ui::PAGE_TRANSITION_TYPED); service->HandleUserInput(url2, SiteEngagementMetrics::ENGAGEMENT_TOUCH_GESTURE); service->HandleMediaPlaying(url3, false); histograms.ExpectTotalCount(SiteEngagementMetrics::kTotalEngagementHistogram, 2); histograms.ExpectBucketCount(SiteEngagementMetrics::kTotalOriginsHistogram, 1, 1); histograms.ExpectBucketCount(SiteEngagementMetrics::kTotalOriginsHistogram, 3, 1); histograms.ExpectTotalCount(SiteEngagementMetrics::kMeanEngagementHistogram, 2); histograms.ExpectTotalCount(SiteEngagementMetrics::kMedianEngagementHistogram, 2); // Recorded per origin. histograms.ExpectTotalCount(SiteEngagementMetrics::kEngagementScoreHistogram, 4); histograms.ExpectUniqueSample( SiteEngagementMetrics::kOriginsWithMaxEngagementHistogram, 0, 2); histograms.ExpectUniqueSample( SiteEngagementMetrics::kOriginsWithMaxDailyEngagementHistogram, 0, 2); histograms.ExpectUniqueSample( SiteEngagementMetrics::kPercentOriginsWithMaxEngagementHistogram, 0, 2); histograms.ExpectTotalCount(SiteEngagementMetrics::kEngagementTypeHistogram, 9); histograms.ExpectBucketCount(SiteEngagementMetrics::kEngagementTypeHistogram, SiteEngagementMetrics::ENGAGEMENT_NAVIGATION, 4); histograms.ExpectBucketCount(SiteEngagementMetrics::kEngagementTypeHistogram, SiteEngagementMetrics::ENGAGEMENT_KEYPRESS, 1); histograms.ExpectBucketCount(SiteEngagementMetrics::kEngagementTypeHistogram, SiteEngagementMetrics::ENGAGEMENT_MOUSE, 1); histograms.ExpectBucketCount(SiteEngagementMetrics::kEngagementTypeHistogram, SiteEngagementMetrics::ENGAGEMENT_TOUCH_GESTURE, 1); histograms.ExpectBucketCount(SiteEngagementMetrics::kEngagementTypeHistogram, SiteEngagementMetrics::ENGAGEMENT_MEDIA_VISIBLE, 1); histograms.ExpectBucketCount(SiteEngagementMetrics::kEngagementTypeHistogram, SiteEngagementMetrics::ENGAGEMENT_MEDIA_HIDDEN, 1); service->HandleNavigation(url1, ui::PAGE_TRANSITION_GENERATED); service->HandleNavigation(url1, ui::PAGE_TRANSITION_TYPED); service->HandleUserInput(url2, SiteEngagementMetrics::ENGAGEMENT_WHEEL); service->HandleUserInput(url1, SiteEngagementMetrics::ENGAGEMENT_KEYPRESS); service->HandleUserInput(url3, SiteEngagementMetrics::ENGAGEMENT_MOUSE); histograms.ExpectTotalCount(SiteEngagementMetrics::kEngagementTypeHistogram, 14); histograms.ExpectBucketCount(SiteEngagementMetrics::kEngagementTypeHistogram, SiteEngagementMetrics::ENGAGEMENT_NAVIGATION, 6); histograms.ExpectBucketCount(SiteEngagementMetrics::kEngagementTypeHistogram, SiteEngagementMetrics::ENGAGEMENT_KEYPRESS, 2); histograms.ExpectBucketCount(SiteEngagementMetrics::kEngagementTypeHistogram, SiteEngagementMetrics::ENGAGEMENT_MOUSE, 2); histograms.ExpectBucketCount(SiteEngagementMetrics::kEngagementTypeHistogram, SiteEngagementMetrics::ENGAGEMENT_TOUCH_GESTURE, 1); histograms.ExpectBucketCount(SiteEngagementMetrics::kEngagementTypeHistogram, SiteEngagementMetrics::ENGAGEMENT_WHEEL, 1); // Advance an origin to the max for a day and advance the clock an hour before // the last increment before max. Expect the histogram to be updated. for (int i = 0; i < 6; ++i) service->HandleNavigation(url1, ui::PAGE_TRANSITION_TYPED); clock->SetNow(GetReferenceTime() + base::TimeDelta::FromMinutes(121)); service->HandleNavigation(url1, ui::PAGE_TRANSITION_TYPED); histograms.ExpectTotalCount(SiteEngagementMetrics::kTotalEngagementHistogram, 3); histograms.ExpectBucketCount(SiteEngagementMetrics::kTotalOriginsHistogram, 1, 1); histograms.ExpectBucketCount(SiteEngagementMetrics::kTotalOriginsHistogram, 3, 2); histograms.ExpectTotalCount(SiteEngagementMetrics::kMeanEngagementHistogram, 3); histograms.ExpectTotalCount(SiteEngagementMetrics::kMedianEngagementHistogram, 3); histograms.ExpectTotalCount(SiteEngagementMetrics::kEngagementScoreHistogram, 7); histograms.ExpectUniqueSample( SiteEngagementMetrics::kOriginsWithMaxEngagementHistogram, 0, 3); histograms.ExpectBucketCount( SiteEngagementMetrics::kOriginsWithMaxDailyEngagementHistogram, 0, 2); histograms.ExpectBucketCount( SiteEngagementMetrics::kOriginsWithMaxDailyEngagementHistogram, 1, 1); histograms.ExpectUniqueSample( SiteEngagementMetrics::kPercentOriginsWithMaxEngagementHistogram, 0, 3); histograms.ExpectTotalCount(SiteEngagementMetrics::kEngagementTypeHistogram, 21); histograms.ExpectBucketCount(SiteEngagementMetrics::kEngagementTypeHistogram, SiteEngagementMetrics::ENGAGEMENT_NAVIGATION, 13); } // Expect that sites that have reached zero engagement are cleaned up. TEST_F(SiteEngagementServiceTest, CleanupEngagementScores) { base::SimpleTestClock* clock = new base::SimpleTestClock(); scoped_ptr service( new SiteEngagementService(profile(), make_scoped_ptr(clock))); base::Time current_day = GetReferenceTime(); clock->SetNow(current_day); // The https and http versions of www.google.com should be separate. GURL url1("https://www.google.com/"); GURL url2("http://www.google.com/"); EXPECT_EQ(0, service->GetScore(url1)); EXPECT_EQ(0, service->GetScore(url2)); // Add the maximum number of points for one day. service->AddPoints(url1, 5.0); EXPECT_EQ(5.0, service->GetScore(url1)); service->AddPoints(url2, 5.0); EXPECT_EQ(5.0, service->GetScore(url2)); // Add more points by moving to another day. clock->SetNow(GetReferenceTime() + base::TimeDelta::FromDays(1)); service->AddPoints(url1, 5.0); EXPECT_EQ(10.0, service->GetScore(url1)); { // Decay one origin to zero by advancing time and expect the engagement // score to be cleaned up. The other score was changed a day later so it // will not have decayed at all. clock->SetNow( GetReferenceTime() + base::TimeDelta::FromDays(SiteEngagementScore::GetDecayPeriodInDays())); std::map score_map = service->GetScoreMap(); EXPECT_EQ(2u, score_map.size()); EXPECT_EQ(10, score_map[url1]); EXPECT_EQ(0, score_map[url2]); service->CleanupEngagementScores(); score_map = service->GetScoreMap(); EXPECT_EQ(1u, score_map.size()); EXPECT_EQ(10, score_map[url1]); EXPECT_EQ(0, service->GetScore(url2)); } { // Decay the other origin to zero by advancing time and expect the // engagement score to be cleaned up. clock->SetNow(GetReferenceTime() + base::TimeDelta::FromDays( 3 * SiteEngagementScore::GetDecayPeriodInDays())); std::map score_map = service->GetScoreMap(); EXPECT_EQ(1u, score_map.size()); EXPECT_EQ(0, score_map[url1]); service->CleanupEngagementScores(); score_map = service->GetScoreMap(); EXPECT_EQ(0u, score_map.size()); EXPECT_EQ(0, service->GetScore(url1)); } } TEST_F(SiteEngagementServiceTest, NavigationAccumulation) { AddTab(browser(), GURL("about:blank")); GURL url("https://www.google.com/"); SiteEngagementService* service = SiteEngagementServiceFactory::GetForProfile(browser()->profile()); ASSERT_TRUE(service); // Only direct navigation should trigger engagement. NavigateWithTransitionAndExpectHigherScore(service, url, ui::PAGE_TRANSITION_TYPED); NavigateWithTransitionAndExpectHigherScore(service, url, ui::PAGE_TRANSITION_GENERATED); NavigateWithTransitionAndExpectHigherScore(service, url, ui::PAGE_TRANSITION_AUTO_BOOKMARK); NavigateWithTransitionAndExpectHigherScore( service, url, ui::PAGE_TRANSITION_KEYWORD_GENERATED); // Other transition types should not accumulate engagement. NavigateWithTransitionAndExpectEqualScore(service, url, ui::PAGE_TRANSITION_AUTO_TOPLEVEL); NavigateWithTransitionAndExpectEqualScore(service, url, ui::PAGE_TRANSITION_LINK); NavigateWithTransitionAndExpectEqualScore(service, url, ui::PAGE_TRANSITION_RELOAD); NavigateWithTransitionAndExpectEqualScore(service, url, ui::PAGE_TRANSITION_FORM_SUBMIT); } TEST_F(SiteEngagementServiceTest, CleanupOriginsOnHistoryDeletion) { SiteEngagementService* engagement = SiteEngagementServiceFactory::GetForProfile(profile()); ASSERT_TRUE(engagement); GURL origin1("http://www.google.com/"); GURL origin1a("http://www.google.com/search?q=asdf"); GURL origin2("https://drive.google.com/"); GURL origin2a("https://drive.google.com/somedoc"); GURL origin3("http://notdeleted.com/"); 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); history::HistoryService* history = HistoryServiceFactory::GetForProfile( profile(), ServiceAccessType::IMPLICIT_ACCESS); history->AddPage(origin1, yesterday_afternoon, history::SOURCE_BROWSED); history->AddPage(origin1a, today, history::SOURCE_BROWSED); engagement->AddPoints(origin1, 5.0); history->AddPage(origin2, yesterday_afternoon, history::SOURCE_BROWSED); history->AddPage(origin2a, yesterday_afternoon, history::SOURCE_BROWSED); engagement->AddPoints(origin2, 5.0); history->AddPage(origin3, today, history::SOURCE_BROWSED); engagement->AddPoints(origin3, 5.0); EXPECT_EQ(5.0, engagement->GetScore(origin1)); EXPECT_EQ(5.0, engagement->GetScore(origin2)); EXPECT_EQ(5.0, engagement->GetScore(origin3)); { SiteEngagementChangeWaiter waiter(profile()); base::CancelableTaskTracker task_tracker; // Expire origin1, origin2 and origin2a. history->ExpireHistoryBetween(std::set(), yesterday, today, base::Bind(&base::DoNothing), &task_tracker); waiter.Wait(); // origin2 is cleaned up because all its urls are deleted. origin1a is still // in history, maintaining origin1's score. origin3 is untouched. EXPECT_EQ(5.0, engagement->GetScore(origin1)); EXPECT_EQ(0, engagement->GetScore(origin2)); EXPECT_EQ(5.0, engagement->GetScore(origin3)); EXPECT_EQ(10.0, engagement->GetTotalEngagementPoints()); } { SiteEngagementChangeWaiter waiter(profile()); // Expire origin1a. std::vector expire_list; history::ExpireHistoryArgs args; args.urls.insert(origin1a); args.SetTimeRangeForOneDay(today); expire_list.push_back(args); base::CancelableTaskTracker task_tracker; history->ExpireHistory(expire_list, base::Bind(&base::DoNothing), &task_tracker); waiter.Wait(); // Only origin3 remains. EXPECT_EQ(0, engagement->GetScore(origin1)); EXPECT_EQ(0, engagement->GetScore(origin2)); EXPECT_EQ(5.0, engagement->GetScore(origin3)); EXPECT_EQ(5.0, engagement->GetTotalEngagementPoints()); } }