diff --git a/chrome/browser/metrics/tab_stats/tab_stats_data_store.cc b/chrome/browser/metrics/tab_stats/tab_stats_data_store.cc index 66a54d650ab141..bc4aa90a3548a8 100644 --- a/chrome/browser/metrics/tab_stats/tab_stats_data_store.cc +++ b/chrome/browser/metrics/tab_stats/tab_stats_data_store.cc @@ -114,8 +114,10 @@ void TabStatsDataStore::OnTabInteraction(content::WebContents* web_contents) { } } -void TabStatsDataStore::OnTabAudible(content::WebContents* web_contents) { - OnTabAudibleOrVisible(web_contents); +void TabStatsDataStore::OnTabIsAudibleChanged( + content::WebContents* web_contents) { + if (web_contents->IsCurrentlyAudible()) + OnTabAudibleOrVisible(web_contents); } void TabStatsDataStore::RecordSamplingMetaData() { diff --git a/chrome/browser/metrics/tab_stats/tab_stats_data_store.h b/chrome/browser/metrics/tab_stats/tab_stats_data_store.h index 589583a199818a..f04a1b7e6c8b72 100644 --- a/chrome/browser/metrics/tab_stats/tab_stats_data_store.h +++ b/chrome/browser/metrics/tab_stats/tab_stats_data_store.h @@ -93,7 +93,7 @@ class TabStatsDataStore : public TabStatsObserver { void OnTabReplaced(content::WebContents* old_contents, content::WebContents* new_contents) override; void OnTabInteraction(content::WebContents* web_contents) override; - void OnTabAudible(content::WebContents* web_contents) override; + void OnTabIsAudibleChanged(content::WebContents* web_contents) override; void OnTabVisibilityChanged(content::WebContents* web_contents) override; // Update the maximum number of tabs in a single window if |value| exceeds diff --git a/chrome/browser/metrics/tab_stats/tab_stats_data_store_unittest.cc b/chrome/browser/metrics/tab_stats/tab_stats_data_store_unittest.cc index 549eca79b517cb..eebbd279840049 100644 --- a/chrome/browser/metrics/tab_stats/tab_stats_data_store_unittest.cc +++ b/chrome/browser/metrics/tab_stats/tab_stats_data_store_unittest.cc @@ -5,7 +5,6 @@ #include "chrome/browser/metrics/tab_stats/tab_stats_data_store.h" #include "chrome/browser/metrics/tab_stats/tab_stats_tracker.h" -#include "chrome/browser/ui/recently_audible_helper.h" #include "chrome/test/base/chrome_render_view_host_test_harness.h" #include "components/prefs/pref_registry_simple.h" #include "components/prefs/testing_pref_service.h" @@ -27,13 +26,6 @@ class TabStatsDataStoreTest : public ChromeRenderViewHostTestHarness { data_store_ = std::make_unique(&pref_service_); } - std::unique_ptr CreateTestWebContents() { - std::unique_ptr contents = - ChromeRenderViewHostTestHarness::CreateTestWebContents(); - RecentlyAudibleHelper::CreateForWebContents(contents.get()); - return contents; - } - TestingPrefServiceSimple pref_service_; std::unique_ptr data_store_; }; @@ -137,11 +129,10 @@ TEST_F(TabStatsDataStoreTest, TrackTabUsageDuringInterval) { ->second.visible_or_audible_during_interval); // Make one of the WebContents audible. - auto* audible_helper_2 = - RecentlyAudibleHelper::FromWebContents(web_contents_vec[2].get()); - audible_helper_2->SetRecentlyAudibleForTesting(); + content::WebContentsTester::For(web_contents_vec[2].get()) + ->SetIsCurrentlyAudible(true); data_store_->ResetIntervalData(interval_map); - data_store_->OnTabAudible(web_contents_vec[2].get()); + data_store_->OnTabIsAudibleChanged(web_contents_vec[2].get()); EXPECT_TRUE(interval_map->find(web_contents_id_vec[0]) ->second.visible_or_audible_during_interval); EXPECT_FALSE(interval_map->find(web_contents_id_vec[1]) @@ -201,10 +192,9 @@ TEST_F(TabStatsDataStoreTest, OnTabReplaced) { EXPECT_TRUE(interval_map->find(tab_id)->second.interacted_during_interval); // Mark the tab as audible and verify that the corresponding bit is set. - auto* audible_helper_2 = - RecentlyAudibleHelper::FromWebContents(web_contents_2.get()); - audible_helper_2->SetNotRecentlyAudibleForTesting(); - data_store_->OnTabAudible(web_contents_2.get()); + content::WebContentsTester::For(web_contents_2.get()) + ->SetIsCurrentlyAudible(true); + data_store_->OnTabIsAudibleChanged(web_contents_2.get()); EXPECT_TRUE( interval_map->find(tab_id)->second.visible_or_audible_during_interval); diff --git a/chrome/browser/metrics/tab_stats/tab_stats_observer.h b/chrome/browser/metrics/tab_stats/tab_stats_observer.h index ab1626e967ad03..eb42ca34b3a8dc 100644 --- a/chrome/browser/metrics/tab_stats/tab_stats_observer.h +++ b/chrome/browser/metrics/tab_stats/tab_stats_observer.h @@ -47,8 +47,8 @@ class TabStatsObserver : public base::CheckedObserver { // type of interactions. virtual void OnTabInteraction(content::WebContents* web_contents) {} - // Records that a tab became audible. - virtual void OnTabAudible(content::WebContents* web_contents) {} + // Records that a tab's audible state changed. + virtual void OnTabIsAudibleChanged(content::WebContents* web_contents) {} // Records that a tab's visibility changed. virtual void OnTabVisibilityChanged(content::WebContents* web_contents) {} diff --git a/chrome/browser/metrics/tab_stats/tab_stats_tracker.cc b/chrome/browser/metrics/tab_stats/tab_stats_tracker.cc index 14e21bd9444267..8e7f9f6f538440 100644 --- a/chrome/browser/metrics/tab_stats/tab_stats_tracker.cc +++ b/chrome/browser/metrics/tab_stats/tab_stats_tracker.cc @@ -235,6 +235,8 @@ void TabStatsTracker::AddObserverAndSetInitialState( observer->OnTabAdded(wc); if (wc->GetCurrentlyPlayingVideoCount()) observer->OnVideoStartedPlaying(wc); + if (wc->IsCurrentlyAudible()) + observer->OnTabIsAudibleChanged(wc); if (wc->IsFullscreen() && wc->HasActiveEffectivelyFullscreenVideo()) observer->OnMediaEffectivelyFullscreenChanged(wc, true); } @@ -326,6 +328,13 @@ class TabStatsTracker::WebContentsUsageObserver // object starting from here. } + void OnAudioStateChanged(bool audible) override { + for (TabStatsObserver& tab_stats_observer : + tab_stats_tracker_->tab_stats_observers_) { + tab_stats_observer.OnTabIsAudibleChanged(web_contents()); + } + } + void MediaEffectivelyFullscreenChanged(bool is_fullscreen) override { for (TabStatsObserver& tab_stats_observer : tab_stats_tracker_->tab_stats_observers_) { @@ -419,20 +428,6 @@ void TabStatsTracker::OnTabStripModelChanged( } } -void TabStatsTracker::TabChangedAt(content::WebContents* web_contents, - int index, - TabChangeType change_type) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - // Ignore 'loading' changes, we're only interested in audio here. - if (change_type != TabChangeType::kAll) - return; - if (web_contents->IsCurrentlyAudible()) { - for (TabStatsObserver& tab_stats_observer : tab_stats_observers_) { - tab_stats_observer.OnTabAudible(web_contents); - } - } -} - void TabStatsTracker::OnResume() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); reporting_delegate_->ReportTabCountOnResume( diff --git a/chrome/browser/metrics/tab_stats/tab_stats_tracker.h b/chrome/browser/metrics/tab_stats/tab_stats_tracker.h index 69f9b18ea16a3f..a1c6cfc37d2053 100644 --- a/chrome/browser/metrics/tab_stats/tab_stats_tracker.h +++ b/chrome/browser/metrics/tab_stats/tab_stats_tracker.h @@ -150,9 +150,6 @@ class TabStatsTracker : public TabStripModelObserver, TabStripModel* tab_strip_model, const TabStripModelChange& change, const TabStripSelectionChange& selection) override; - void TabChangedAt(content::WebContents* web_contents, - int index, - TabChangeType change_type) override; // base::PowerObserver: void OnResume() override; diff --git a/chrome/browser/metrics/tab_stats/tab_stats_tracker_browsertest.cc b/chrome/browser/metrics/tab_stats/tab_stats_tracker_browsertest.cc index 88321c36aa1d4b..74a1bba47f94e8 100644 --- a/chrome/browser/metrics/tab_stats/tab_stats_tracker_browsertest.cc +++ b/chrome/browser/metrics/tab_stats/tab_stats_tracker_browsertest.cc @@ -23,6 +23,9 @@ #include "content/public/browser/web_contents.h" #include "content/public/test/browser_test.h" #include "content/public/test/content_browser_test_utils.h" +#include "content/public/test/web_contents_tester.h" +#include "media/base/media_switches.h" +#include "media/base/test_data_util.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/display/screen.h" @@ -102,6 +105,12 @@ class TabStatsTrackerBrowserTest : public InProcessBrowserTest { public: TabStatsTrackerBrowserTest() = default; + void SetUpCommandLine(base::CommandLine* command_line) override { + command_line->AppendSwitchASCII( + switches::kAutoplayPolicy, + switches::autoplay::kNoUserGestureRequiredPolicy); + } + void SetUpOnMainThread() override { tab_stats_tracker_ = TabStatsTracker::GetInstance(); ASSERT_TRUE(tab_stats_tracker_ != nullptr); @@ -358,7 +367,7 @@ class LenientMockTabStatsObserver : public TabStatsObserver { void(content::WebContents*, content::WebContents*)); MOCK_METHOD1(OnMainFrameNavigationCommitted, void(content::WebContents*)); MOCK_METHOD1(OnTabInteraction, void(content::WebContents*)); - MOCK_METHOD1(OnTabAudible, void(content::WebContents*)); + MOCK_METHOD1(OnTabIsAudibleChanged, void(content::WebContents*)); MOCK_METHOD1(OnTabVisibilityChanged, void(content::WebContents*)); MOCK_METHOD2(OnMediaEffectivelyFullscreenChanged, void(content::WebContents*, bool)); @@ -505,4 +514,58 @@ IN_PROC_BROWSER_TEST_F(TabStatsTrackerBrowserTest, TabSwitch) { EXPECT_EQ(1U, count_observer.window_count()); } +namespace { + +// Observes a WebContents and waits until it becomes audible. +// both indicate that they are audible. +class AudioStartObserver : public content::WebContentsObserver { + public: + AudioStartObserver(content::WebContents* web_contents, + base::OnceClosure quit_closure) + : content::WebContentsObserver(web_contents), + quit_closure_(std::move(quit_closure)) { + DCHECK(!web_contents->IsCurrentlyAudible()); + } + ~AudioStartObserver() override = default; + + // WebContentsObserver: + void OnAudioStateChanged(bool audible) override { + DCHECK(audible); + std::move(quit_closure_).Run(); + } + + private: + base::OnceClosure quit_closure_; +}; + +} // namespace + +IN_PROC_BROWSER_TEST_F(TabStatsTrackerBrowserTest, AddObserverAudibleTab) { + // Set up the embedded test server to serve the test javascript file. + embedded_test_server()->ServeFilesFromSourceDirectory( + media::GetTestDataPath()); + ASSERT_TRUE(embedded_test_server()->Start()); + + // Open the test JS file in the only WebContents. + auto* web_contents = browser()->tab_strip_model()->GetWebContentsAt(0); + ASSERT_TRUE(NavigateToURL(web_contents, embedded_test_server()->GetURL( + "/webaudio_oscillator.html"))); + + // Start the audio. + base::RunLoop run_loop; + AudioStartObserver audio_start_observer(web_contents, run_loop.QuitClosure()); + EXPECT_EQ("OK", EvalJsWithManualReply(web_contents, "StartOscillator();")); + run_loop.Run(); + + // Adding an observer now should receive the OnTabIsAudibleChanged() call. + MockTabStatsObserver mock_observer; + EXPECT_CALL(mock_observer, OnWindowAdded()); + EXPECT_CALL(mock_observer, OnTabAdded(web_contents)); + EXPECT_CALL(mock_observer, OnTabIsAudibleChanged(web_contents)); + tab_stats_tracker_->AddObserverAndSetInitialState(&mock_observer); + + // Clean up. + tab_stats_tracker_->RemoveObserver(&mock_observer); +} + } // namespace metrics diff --git a/chrome/browser/metrics/usage_scenario/tab_usage_scenario_tracker.cc b/chrome/browser/metrics/usage_scenario/tab_usage_scenario_tracker.cc index 1a61bb618e7060..0441ef9c47433c 100644 --- a/chrome/browser/metrics/usage_scenario/tab_usage_scenario_tracker.cc +++ b/chrome/browser/metrics/usage_scenario/tab_usage_scenario_tracker.cc @@ -82,6 +82,8 @@ void TabUsageScenarioTracker::OnTabReplaced( // Start tracking |new_contents| if needed. if (new_contents->GetVisibility() == content::Visibility::VISIBLE) OnTabVisibilityChanged(new_contents); + if (new_contents->IsCurrentlyAudible()) + usage_scenario_data_store_->OnAudioStarts(); } void TabUsageScenarioTracker::OnTabVisibilityChanged( @@ -114,6 +116,16 @@ void TabUsageScenarioTracker::OnTabInteraction( usage_scenario_data_store_->OnUserInteraction(); } +void TabUsageScenarioTracker::OnTabIsAudibleChanged( + content::WebContents* web_contents) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (web_contents->IsCurrentlyAudible()) { + usage_scenario_data_store_->OnAudioStarts(); + } else { + usage_scenario_data_store_->OnAudioStops(); + } +} + void TabUsageScenarioTracker::OnMediaEffectivelyFullscreenChanged( content::WebContents* web_contents, bool is_fullscreen) { @@ -257,6 +269,8 @@ void TabUsageScenarioTracker::OnWebContentsRemoved( if (video_iter != contents_playing_video_.end()) { contents_playing_video_.erase(video_iter); } + if (web_contents->IsCurrentlyAudible()) + usage_scenario_data_store_->OnAudioStops(); } void TabUsageScenarioTracker::InsertContentsInMapOfVisibleTabs( diff --git a/chrome/browser/metrics/usage_scenario/tab_usage_scenario_tracker.h b/chrome/browser/metrics/usage_scenario/tab_usage_scenario_tracker.h index 9f255c14c4012d..ebd55f01c4e1f2 100644 --- a/chrome/browser/metrics/usage_scenario/tab_usage_scenario_tracker.h +++ b/chrome/browser/metrics/usage_scenario/tab_usage_scenario_tracker.h @@ -40,6 +40,7 @@ class TabUsageScenarioTracker : public TabStatsObserver, content::WebContents* new_contents) override; void OnTabVisibilityChanged(content::WebContents* web_contents) override; void OnTabInteraction(content::WebContents* web_contents) override; + void OnTabIsAudibleChanged(content::WebContents* web_contents) override; void OnMediaEffectivelyFullscreenChanged(content::WebContents* web_contents, bool is_fullscreen) override; void OnMainFrameNavigationCommitted( diff --git a/chrome/browser/metrics/usage_scenario/tab_usage_scenario_tracker_unittest.cc b/chrome/browser/metrics/usage_scenario/tab_usage_scenario_tracker_unittest.cc index c0a41efe1a9fdf..4e2a0bc9843695 100644 --- a/chrome/browser/metrics/usage_scenario/tab_usage_scenario_tracker_unittest.cc +++ b/chrome/browser/metrics/usage_scenario/tab_usage_scenario_tracker_unittest.cc @@ -295,7 +295,6 @@ TEST_F(TabUsageScenarioTrackerTest, VisibleTabPlayingVideoRemoved) { // Pretend that |content1| is playing a video while being visible. tab_usage_scenario_tracker_->OnVideoStartedPlaying(contents1.get()); - static constexpr base::TimeDelta kInterval = base::TimeDelta::FromMinutes(2); task_environment()->FastForwardBy(kInterval); UsageScenarioDataStore::IntervalData interval_data = usage_scenario_data_store_.ResetIntervalData(); @@ -307,6 +306,25 @@ TEST_F(TabUsageScenarioTrackerTest, VisibleTabPlayingVideoRemoved) { EXPECT_TRUE(interval_data.time_playing_video_in_visible_tab.is_zero()); } +TEST_F(TabUsageScenarioTrackerTest, TabPlayingAudio) { + // Create a tab and add it. + auto web_contents = CreateWebContents(); + tab_usage_scenario_tracker_->OnTabAdded(web_contents.get()); + + // Pretend that web_contents is playing audio. + content::WebContentsTester::For(web_contents.get()) + ->SetIsCurrentlyAudible(true); + tab_usage_scenario_tracker_->OnTabIsAudibleChanged(web_contents.get()); + + task_environment()->FastForwardBy(kInterval); + + // Grab the interval data and ensure that the time playing a video in the + // visible tab is properly recorded. + UsageScenarioDataStore::IntervalData interval_data = + usage_scenario_data_store_.ResetIntervalData(); + EXPECT_EQ(interval_data.time_playing_audio, kInterval); +} + TEST_F(TabUsageScenarioTrackerTest, UKMVisibility1tab) { const GURL kUrl1("https://foo.com/subfoo"); const GURL kUrl2("https://bar.com/subbar"); diff --git a/chrome/browser/metrics/usage_scenario/usage_scenario_data_store.cc b/chrome/browser/metrics/usage_scenario/usage_scenario_data_store.cc index 6b33c01f4541f9..2f34af82543364 100644 --- a/chrome/browser/metrics/usage_scenario/usage_scenario_data_store.cc +++ b/chrome/browser/metrics/usage_scenario/usage_scenario_data_store.cc @@ -62,6 +62,10 @@ UsageScenarioDataStoreImpl::ResetIntervalData() { capturing_video_since_ = now; } + if (!playing_audio_since_.is_null()) { + playing_audio_since_ = now; + } + if (!playing_video_in_active_tab_since_.is_null()) { playing_video_in_active_tab_since_ = now; } @@ -126,7 +130,7 @@ void UsageScenarioDataStoreImpl::OnFullScreenVideoEndsOnSingleMonitor() { void UsageScenarioDataStoreImpl::OnWebRTCConnectionOpened() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - // Grab the current timestamp if there's no remaining WebRTC connection. + // Grab the current timestamp if there's no other WebRTC connection. if (webrtc_open_connection_count_ == 0) { DCHECK(has_opened_webrtc_connection_since_.is_null()); has_opened_webrtc_connection_since_ = tick_clock_->NowTicks(); @@ -177,6 +181,33 @@ void UsageScenarioDataStoreImpl::OnIsCapturingVideoEnded() { } } +void UsageScenarioDataStoreImpl::OnAudioStarts() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + // Grab the current timestamp if there's no other tabs playing audio. + if (tabs_playing_audio_ == 0) { + DCHECK(playing_audio_since_.is_null()); + playing_audio_since_ = base::TimeTicks::Now(); + } + ++tabs_playing_audio_; + DCHECK_GE(current_tab_count_, tabs_playing_audio_); +} + +void UsageScenarioDataStoreImpl::OnAudioStops() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK_GT(tabs_playing_audio_, 0U); + --tabs_playing_audio_; + DCHECK_GE(current_tab_count_, tabs_playing_audio_); + + // If this was the last tab playing audio then the interval data should be + // updated. + if (tabs_playing_audio_ == 0) { + DCHECK(!playing_audio_since_.is_null()); + interval_data_.time_playing_audio += + base::TimeTicks::Now() - playing_audio_since_; + playing_audio_since_ = base::TimeTicks(); + } +} + void UsageScenarioDataStoreImpl::OnVideoStartsInVisibleTab() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); ++visible_tabs_playing_video_; @@ -249,6 +280,10 @@ void UsageScenarioDataStoreImpl::FinalizeIntervalData(base::TimeTicks now) { interval_data_.time_capturing_video += now - capturing_video_since_; } + if (!playing_audio_since_.is_null()) { + interval_data_.time_playing_audio += now - playing_audio_since_; + } + if (!playing_video_in_active_tab_since_.is_null()) { interval_data_.time_playing_video_in_visible_tab += now - playing_video_in_active_tab_since_; diff --git a/chrome/browser/metrics/usage_scenario/usage_scenario_data_store.h b/chrome/browser/metrics/usage_scenario/usage_scenario_data_store.h index 16fa12cafd33e3..750a66f6130020 100644 --- a/chrome/browser/metrics/usage_scenario/usage_scenario_data_store.h +++ b/chrome/browser/metrics/usage_scenario/usage_scenario_data_store.h @@ -64,6 +64,8 @@ class UsageScenarioDataStore base::TimeDelta time_capturing_video; // The time spent playing video in at least one visible tab. base::TimeDelta time_playing_video_in_visible_tab; + // The time spent playing audio in at least one tab. + base::TimeDelta time_playing_audio; // The time since the last user interaction with the browser at the end of // the interval. This time can exceed the length of the interval. base::TimeDelta time_since_last_user_interaction_with_browser; @@ -127,6 +129,8 @@ class UsageScenarioDataStoreImpl : public UsageScenarioDataStore { void OnWebRTCConnectionClosed(); void OnIsCapturingVideoStarted(); void OnIsCapturingVideoEnded(); + void OnAudioStarts(); + void OnAudioStops(); // Should be called when a video starts in a visible tab or when a non visible // tab playing video becomes visible. @@ -206,6 +210,14 @@ class UsageScenarioDataStoreImpl : public UsageScenarioDataStore { // ends (when ResetIntervalData is called). base::TimeTicks capturing_video_since_; + // The number of tabs playing audio. + uint16_t tabs_playing_audio_ = 0; + + // The timestamp of the beginning of an audio session that has caused + // |tabs_playing_audio_| to increase to 1. Reset to |now| when an interval + // ends (when ResetIntervalData is called). + base::TimeTicks playing_audio_since_; + // The number of visible tabs playing at least one video. uint16_t visible_tabs_playing_video_ = 0; diff --git a/chrome/browser/metrics/usage_scenario/usage_scenario_data_store_unittest.cc b/chrome/browser/metrics/usage_scenario/usage_scenario_data_store_unittest.cc index 6b1476a944d6b6..79c430a08fd569 100644 --- a/chrome/browser/metrics/usage_scenario/usage_scenario_data_store_unittest.cc +++ b/chrome/browser/metrics/usage_scenario/usage_scenario_data_store_unittest.cc @@ -457,3 +457,30 @@ TEST_F(UsageScenarioDataStoreTest, PlayingVideoInVisibleTab) { data = ResetIntervalData(); EXPECT_EQ(base::TimeDelta(), data.time_playing_video_in_visible_tab); } + +TEST_F(UsageScenarioDataStoreTest, PlayingAudio) { + data_store()->OnTabAdded(); + data_store()->OnTabAdded(); + + task_environment_.FastForwardBy(kShortDelay); + + data_store()->OnAudioStarts(); + task_environment_.FastForwardBy(kShortDelay); + + auto data = ResetIntervalData(); + EXPECT_EQ(kShortDelay, data.time_playing_audio); + + data_store()->OnAudioStarts(); + task_environment_.FastForwardBy(kShortDelay); + + data_store()->OnAudioStops(); + data_store()->OnAudioStops(); + task_environment_.FastForwardBy(kShortDelay); + + data = ResetIntervalData(); + EXPECT_EQ(kShortDelay, data.time_playing_audio); + + task_environment_.FastForwardBy(kShortDelay); + data = ResetIntervalData(); + EXPECT_EQ(base::TimeDelta(), data.time_playing_audio); +}