blob: 0441ef9c47433cfd26d4b3a3712de1cb27517ab6 [file] [log] [blame]
// Copyright 2021 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/metrics/usage_scenario/tab_usage_scenario_tracker.h"
#include "base/containers/contains.h"
#include "chrome/browser/metrics/usage_scenario/usage_scenario_data_store.h"
#include "components/ukm/content/source_url_recorder.h"
#include "content/public/browser/visibility.h"
#include "content/public/browser/web_contents.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "ui/display/screen.h"
#include "url/origin.h"
namespace metrics {
namespace {
std::pair<ukm::SourceId, url::Origin> GetNavigationInfoForContents(
content::WebContents* contents) {
auto* main_frame = contents->GetMainFrame();
if (!main_frame || main_frame->GetLastCommittedURL().is_empty())
return std::make_pair(ukm::kInvalidSourceId, url::Origin());
return std::make_pair(ukm::GetSourceIdForWebContentsDocument(contents),
main_frame->GetLastCommittedOrigin());
}
} // namespace
TabUsageScenarioTracker::TabUsageScenarioTracker(
UsageScenarioDataStoreImpl* usage_scenario_data_store)
: usage_scenario_data_store_(usage_scenario_data_store) {
// TODO(crbug.com/1153193): Owners of this class have to set the initial
// state. Constructing the object like this starts off the state as empty. If
// tabs/windows already exist when this object is created they need to be
// added using the normal functions after creation.
auto* screen = display::Screen::GetScreen();
// Make sure that this doesn't get created before setting up the global Screen
// instance.
DCHECK(screen);
screen->AddObserver(this);
}
TabUsageScenarioTracker::~TabUsageScenarioTracker() {
auto* screen = display::Screen::GetScreen();
// Make sure that this doesn't get destroyed after destroying the global
// screen instance.
DCHECK(screen);
screen->RemoveObserver(this);
}
void TabUsageScenarioTracker::OnTabAdded(content::WebContents* web_contents) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
usage_scenario_data_store_->OnTabAdded();
// Tab is added already visible. It will not get a separate visibility update
// so we handle the visibility here.
if (web_contents->GetVisibility() == content::Visibility::VISIBLE) {
DCHECK(!base::Contains(visible_tabs_, web_contents));
usage_scenario_data_store_->OnWindowVisible();
InsertContentsInMapOfVisibleTabs(web_contents);
}
}
void TabUsageScenarioTracker::OnTabRemoved(content::WebContents* web_contents) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
OnWebContentsRemoved(web_contents);
usage_scenario_data_store_->OnTabClosed();
}
void TabUsageScenarioTracker::OnTabReplaced(
content::WebContents* old_contents,
content::WebContents* new_contents) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
OnWebContentsRemoved(old_contents);
DCHECK(!base::Contains(visible_tabs_, old_contents));
DCHECK(!base::Contains(contents_playing_video_, old_contents));
DCHECK_NE(content_with_media_playing_fullscreen_, old_contents);
// 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(
content::WebContents* web_contents) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto iter = visible_tabs_.find(web_contents);
// The first content::Visibility::VISIBLE notification is always sent, even
// if the tab starts in the visible state.
if (iter == visible_tabs_.end() &&
web_contents->GetVisibility() == content::Visibility::VISIBLE) {
usage_scenario_data_store_->OnWindowVisible();
// If this tab is playing video then record that it became visible.
if (base::Contains(contents_playing_video_, web_contents)) {
usage_scenario_data_store_->OnVideoStartsInVisibleTab();
}
InsertContentsInMapOfVisibleTabs(web_contents);
} else if (iter != visible_tabs_.end() &&
web_contents->GetVisibility() != content::Visibility::VISIBLE) {
// The tab was previously visible and it's now hidden or occluded.
OnTabBecameHidden(&iter);
}
}
void TabUsageScenarioTracker::OnTabInteraction(
content::WebContents* web_contents) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
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) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto* screen = display::Screen::GetScreen();
DCHECK(screen);
if (screen->GetNumDisplays() == 1) {
if (is_fullscreen) {
DCHECK(!content_with_media_playing_fullscreen_);
content_with_media_playing_fullscreen_ = web_contents;
usage_scenario_data_store_->OnFullScreenVideoStartsOnSingleMonitor();
} else {
OnContentStoppedPlayingMediaFullScreen();
}
}
}
void TabUsageScenarioTracker::OnMainFrameNavigationCommitted(
content::WebContents* web_contents) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
usage_scenario_data_store_->OnTopLevelNavigation();
if (web_contents->GetVisibility() == content::Visibility::VISIBLE) {
auto iter = visible_tabs_.find(web_contents);
DCHECK(iter != visible_tabs_.end());
// If there's already an entry with a valid SourceID for this in
// |visible_tabs_| then it means that there's been a main frame navigation
// for a visible tab. Records that the SourceID previously associated with
// this tab isn't visible anymore.
if (iter->second.first != ukm::kInvalidSourceId) {
usage_scenario_data_store_->OnUkmSourceBecameHidden(iter->second.first,
iter->second.second);
}
iter->second = GetNavigationInfoForContents(web_contents);
if (iter->second.first != ukm::kInvalidSourceId) {
usage_scenario_data_store_->OnUkmSourceBecameVisible(iter->second.first,
iter->second.second);
}
}
}
void TabUsageScenarioTracker::OnVideoStartedPlaying(
content::WebContents* web_contents) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!base::Contains(contents_playing_video_, web_contents));
contents_playing_video_.insert(web_contents);
if (base::Contains(visible_tabs_, web_contents))
usage_scenario_data_store_->OnVideoStartsInVisibleTab();
}
void TabUsageScenarioTracker::OnVideoStoppedPlaying(
content::WebContents* web_contents) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(base::Contains(contents_playing_video_, web_contents));
contents_playing_video_.erase(web_contents);
if (base::Contains(visible_tabs_, web_contents))
usage_scenario_data_store_->OnVideoStopsInVisibleTab();
}
void TabUsageScenarioTracker::OnDisplayAdded(const display::Display& unused) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto* screen = display::Screen::GetScreen();
if (screen->GetNumDisplays() == 1) {
if (content_with_media_playing_fullscreen_ != nullptr) {
DCHECK(usage_scenario_data_store_
->is_playing_full_screen_video_single_monitor_since()
.is_null());
usage_scenario_data_store_->OnFullScreenVideoStartsOnSingleMonitor();
}
return;
}
// Stop the fullscreen video on single monitor event if there's more than one
// screen.
if (!usage_scenario_data_store_
->is_playing_full_screen_video_single_monitor_since()
.is_null()) {
usage_scenario_data_store_->OnFullScreenVideoEndsOnSingleMonitor();
}
}
void TabUsageScenarioTracker::OnDisplayRemoved(const display::Display& unused) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto* screen = display::Screen::GetScreen();
DCHECK(screen);
// Update the data store if there's only one display now running media
// fullscreen.
if (screen->GetNumDisplays() == 1 &&
content_with_media_playing_fullscreen_ != nullptr) {
DCHECK(usage_scenario_data_store_
->is_playing_full_screen_video_single_monitor_since()
.is_null());
usage_scenario_data_store_->OnFullScreenVideoStartsOnSingleMonitor();
}
}
void TabUsageScenarioTracker::OnContentStoppedPlayingMediaFullScreen() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
usage_scenario_data_store_->OnFullScreenVideoEndsOnSingleMonitor();
content_with_media_playing_fullscreen_ = nullptr;
}
void TabUsageScenarioTracker::OnTabBecameHidden(
VisibleTabsMap::iterator* visible_tab_iter) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// If this tab is playing video then record that it became non visible.
if (base::Contains(contents_playing_video_, (*visible_tab_iter)->first)) {
usage_scenario_data_store_->OnVideoStopsInVisibleTab();
}
// |OnMediaEffectivelyFullscreenChanged| doesn't get called if a tab playing
// media fullscreen gets closed.
if ((*visible_tab_iter)->first == content_with_media_playing_fullscreen_)
OnContentStoppedPlayingMediaFullScreen();
// Record that the ukm::SourceID associated with this tab isn't visible
// anymore if necessary.
if ((*visible_tab_iter)->second.first != ukm::kInvalidSourceId) {
usage_scenario_data_store_->OnUkmSourceBecameHidden(
(*visible_tab_iter)->second.first, (*visible_tab_iter)->second.second);
}
visible_tabs_.erase(*visible_tab_iter);
usage_scenario_data_store_->OnWindowHidden();
}
void TabUsageScenarioTracker::OnWebContentsRemoved(
content::WebContents* web_contents) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto iter = visible_tabs_.find(web_contents);
DCHECK_EQ(iter != visible_tabs_.end(),
web_contents->GetVisibility() == content::Visibility::VISIBLE);
auto video_iter = contents_playing_video_.find(web_contents);
// If |web_contents| is tracked in the list of visible WebContents then a
// synthetic visibility change event should be emitted.
if (iter != visible_tabs_.end()) {
OnTabBecameHidden(&iter);
}
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(
content::WebContents* web_contents) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!base::Contains(visible_tabs_, web_contents));
auto iter = visible_tabs_.emplace(web_contents,
GetNavigationInfoForContents(web_contents));
if (iter.first->second.first != ukm::kInvalidSourceId) {
usage_scenario_data_store_->OnUkmSourceBecameVisible(
iter.first->second.first, iter.first->second.second);
}
}
} // namespace metrics