Skip to content

Commit

Permalink
[Battery] Track tabs playing audio
Browse files Browse the repository at this point in the history
This CL adds OnTabIsAudibleChanged() to replaces OnTabAudible()
in TabStatsObserver to allow the calculation of the precise
timing of when a tab starts and stops playing audio.

Bug: 1153193
Change-Id: I2fb89beb6e31f8853f534db83db8d2e3e5b71c7a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2716924
Commit-Queue: Patrick Monette <pmonette@chromium.org>
Reviewed-by: Sébastien Marchand <sebmarchand@chromium.org>
Cr-Commit-Position: refs/heads/master@{#859166}
  • Loading branch information
plmonette-zz authored and Chromium LUCI CQ committed Mar 2, 2021
1 parent 6a31963 commit 9251d88
Show file tree
Hide file tree
Showing 13 changed files with 195 additions and 41 deletions.
6 changes: 4 additions & 2 deletions chrome/browser/metrics/tab_stats/tab_stats_data_store.cc
Expand Up @@ -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() {
Expand Down
2 changes: 1 addition & 1 deletion chrome/browser/metrics/tab_stats/tab_stats_data_store.h
Expand Up @@ -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
Expand Down
22 changes: 6 additions & 16 deletions chrome/browser/metrics/tab_stats/tab_stats_data_store_unittest.cc
Expand Up @@ -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"
Expand All @@ -27,13 +26,6 @@ class TabStatsDataStoreTest : public ChromeRenderViewHostTestHarness {
data_store_ = std::make_unique<TabStatsDataStore>(&pref_service_);
}

std::unique_ptr<content::WebContents> CreateTestWebContents() {
std::unique_ptr<content::WebContents> contents =
ChromeRenderViewHostTestHarness::CreateTestWebContents();
RecentlyAudibleHelper::CreateForWebContents(contents.get());
return contents;
}

TestingPrefServiceSimple pref_service_;
std::unique_ptr<TabStatsDataStore> data_store_;
};
Expand Down Expand Up @@ -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])
Expand Down Expand Up @@ -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);

Expand Down
4 changes: 2 additions & 2 deletions chrome/browser/metrics/tab_stats/tab_stats_observer.h
Expand Up @@ -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) {}
Expand Down
23 changes: 9 additions & 14 deletions chrome/browser/metrics/tab_stats/tab_stats_tracker.cc
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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_) {
Expand Down Expand Up @@ -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(
Expand Down
3 changes: 0 additions & 3 deletions chrome/browser/metrics/tab_stats/tab_stats_tracker.h
Expand Up @@ -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;

Expand Down
Expand Up @@ -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"
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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
Expand Up @@ -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(
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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(
Expand Down
Expand Up @@ -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(
Expand Down
Expand Up @@ -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();
Expand All @@ -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");
Expand Down
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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_;
Expand Down Expand Up @@ -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_;
Expand Down

0 comments on commit 9251d88

Please sign in to comment.