blob: 85fc5cc8f37968b703c15002d9ac3f1c25b03562 [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/resource_coordinator/tab_metrics_logger.h"
#include <algorithm>
#include <string>
#include "base/check_op.h"
#include "base/notreached.h"
#include "base/stl_util.h"
#include "base/time/time.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/engagement/site_engagement_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/resource_coordinator/tab_lifecycle_unit_external.h"
#include "chrome/browser/resource_coordinator/tab_manager_features.h"
#include "chrome/browser/resource_coordinator/tab_metrics_event.pb.h"
#include "chrome/browser/resource_coordinator/tab_ranker/mru_features.h"
#include "chrome/browser/resource_coordinator/tab_ranker/tab_features.h"
#include "chrome/browser/resource_coordinator/tab_ranker/window_features.h"
#include "chrome/browser/resource_coordinator/utils.h"
#include "chrome/browser/tab_contents/form_interaction_tab_helper.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/recently_audible_helper.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "net/base/mime_util.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "third_party/blink/public/mojom/frame/sudden_termination_disabler_type.mojom.h"
#include "url/gurl.h"
using metrics::TabMetricsEvent;
using metrics::WindowMetricsEvent;
namespace {
// Returns the number of discards that happened to |contents|.
int GetDiscardCount(content::WebContents* contents) {
auto* external =
resource_coordinator::TabLifecycleUnitExternal::FromWebContents(contents);
DCHECK(external);
return external->GetDiscardCount();
}
// Populates navigation-related metrics.
void PopulatePageTransitionFeatures(ui::PageTransition page_transition,
tab_ranker::TabFeatures* tab) {
// We only report the following core types.
// Note: Redirects unrelated to clicking a link still get the "link" type.
if (ui::PageTransitionCoreTypeIs(page_transition, ui::PAGE_TRANSITION_LINK) ||
ui::PageTransitionCoreTypeIs(page_transition,
ui::PAGE_TRANSITION_AUTO_BOOKMARK) ||
ui::PageTransitionCoreTypeIs(page_transition,
ui::PAGE_TRANSITION_FORM_SUBMIT) ||
ui::PageTransitionCoreTypeIs(page_transition,
ui::PAGE_TRANSITION_RELOAD)) {
tab->page_transition_core_type =
ui::PageTransitionStripQualifier(page_transition);
}
tab->page_transition_from_address_bar =
(page_transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) != 0;
tab->page_transition_is_redirect =
ui::PageTransitionIsRedirect(page_transition);
}
// Populates TabFeatures from |page_metrics|.
void PopulateTabFeaturesFromPageMetrics(
const TabMetricsLogger::PageMetrics& page_metrics,
tab_ranker::TabFeatures* tab) {
static_assert(sizeof(TabMetricsLogger::PageMetrics) ==
sizeof(int) * 4 + sizeof(ui::PageTransition),
"Make sure all fields in PageMetrics are considered here.");
tab->key_event_count = page_metrics.key_event_count;
tab->mouse_event_count = page_metrics.mouse_event_count;
tab->num_reactivations = page_metrics.num_reactivations;
tab->touch_event_count = page_metrics.touch_event_count;
PopulatePageTransitionFeatures(page_metrics.page_transition, tab);
}
// Populates TabFeatures that can be calculated simply from |web_contents|.
void PopulateTabFeaturesFromWebContents(content::WebContents* web_contents,
tab_ranker::TabFeatures* tab_features) {
tab_features->has_before_unload_handler =
web_contents->GetMainFrame()->GetSuddenTerminationDisablerState(
blink::mojom::SuddenTerminationDisablerType::kBeforeUnloadHandler);
tab_features->has_form_entry =
FormInteractionTabHelper::FromWebContents(web_contents)
->had_form_interaction();
tab_features->host = web_contents->GetLastCommittedURL().host();
tab_features->navigation_entry_count =
web_contents->GetController().GetEntryCount();
if (SiteEngagementService::IsEnabled()) {
tab_features->site_engagement_score =
TabMetricsLogger::GetSiteEngagementScore(web_contents);
}
// This checks if the tab was audible within the past two seconds, same as the
// audio indicator in the tab strip.
tab_features->was_recently_audible =
RecentlyAudibleHelper::FromWebContents(web_contents)
->WasRecentlyAudible();
tab_features->discard_count = GetDiscardCount(web_contents);
}
// Populates TabFeatures that calculated from |browser| including WindowMetrics
// and PinState.
void PopulateTabFeaturesFromBrowser(const Browser* browser,
content::WebContents* web_contents,
tab_ranker::TabFeatures* tab_features) {
// For pin state.
const TabStripModel* tab_strip_model = browser->tab_strip_model();
int index = tab_strip_model->GetIndexOfWebContents(web_contents);
DCHECK_NE(index, TabStripModel::kNoTab);
tab_features->is_pinned = tab_strip_model->IsTabPinned(index);
// For window features.
tab_ranker::WindowFeatures window =
TabMetricsLogger::CreateWindowFeatures(browser);
tab_features->window_is_active = window.is_active;
tab_features->window_show_state = window.show_state;
tab_features->window_tab_count = window.tab_count;
tab_features->window_type = window.type;
}
} // namespace
TabMetricsLogger::TabMetricsLogger() = default;
TabMetricsLogger::~TabMetricsLogger() = default;
// static
int TabMetricsLogger::GetSiteEngagementScore(
content::WebContents* web_contents) {
if (!SiteEngagementService::IsEnabled())
return -1;
SiteEngagementService* service = SiteEngagementService::Get(
Profile::FromBrowserContext(web_contents->GetBrowserContext()));
DCHECK(service);
// Scores range from 0 to 100. Round down to a multiple of 10 to conform to
// privacy guidelines.
double raw_score = service->GetScore(web_contents->GetVisibleURL());
int rounded_score = static_cast<int>(raw_score / 10) * 10;
DCHECK_LE(0, rounded_score);
DCHECK_GE(100, rounded_score);
return rounded_score;
}
// static
base::Optional<tab_ranker::TabFeatures> TabMetricsLogger::GetTabFeatures(
const PageMetrics& page_metrics,
content::WebContents* web_contents) {
Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
if (!browser)
return base::nullopt;
tab_ranker::TabFeatures tab;
PopulateTabFeaturesFromWebContents(web_contents, &tab);
PopulateTabFeaturesFromBrowser(browser, web_contents, &tab);
PopulateTabFeaturesFromPageMetrics(page_metrics, &tab);
return tab;
}
void TabMetricsLogger::LogTabMetrics(
ukm::SourceId ukm_source_id,
const tab_ranker::TabFeatures& tab_features,
content::WebContents* web_contents,
int64_t label_id) {
if (!ukm_source_id)
return;
if (web_contents) {
// UKM recording is disabled in OTR.
if (web_contents->GetBrowserContext()->IsOffTheRecord())
return;
// Verify that the browser is not closing.
const Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
if (base::Contains(BrowserList::GetInstance()->currently_closing_browsers(),
browser)) {
return;
}
const TabStripModel* tab_strip_model = browser->tab_strip_model();
if (tab_strip_model->closing_all())
return;
}
ukm::builders::TabManager_TabMetrics entry(ukm_source_id);
PopulateTabFeaturesToUkmEntry(tab_features, &entry);
entry.SetLabelId(label_id);
entry.SetQueryId(query_id_);
entry.Record(ukm::UkmRecorder::Get());
}
void TabMetricsLogger::LogForegroundedOrClosedMetrics(
ukm::SourceId ukm_source_id,
const ForegroundedOrClosedMetrics& metrics) {
if (!ukm_source_id)
return;
ukm::builders::TabManager_Background_ForegroundedOrClosed(ukm_source_id)
.SetLabelId(metrics.label_id)
.SetIsForegrounded(metrics.is_foregrounded)
.SetTimeFromBackgrounded(metrics.time_from_backgrounded)
.SetIsDiscarded(metrics.is_discarded)
.Record(ukm::UkmRecorder::Get());
}
void TabMetricsLogger::LogTabLifetime(ukm::SourceId ukm_source_id,
base::TimeDelta time_since_navigation) {
if (!ukm_source_id)
return;
ukm::builders::TabManager_TabLifetime(ukm_source_id)
.SetTimeSinceNavigation(time_since_navigation.InMilliseconds())
.Record(ukm::UkmRecorder::Get());
}
// static
tab_ranker::WindowFeatures TabMetricsLogger::CreateWindowFeatures(
const Browser* browser) {
DCHECK(browser->window());
WindowMetricsEvent::Type window_type = WindowMetricsEvent::TYPE_UNKNOWN;
switch (browser->type()) {
case Browser::TYPE_NORMAL:
window_type = WindowMetricsEvent::TYPE_TABBED;
break;
case Browser::TYPE_POPUP:
window_type = WindowMetricsEvent::TYPE_POPUP;
break;
case Browser::TYPE_APP:
case Browser::TYPE_APP_POPUP:
window_type = WindowMetricsEvent::TYPE_APP;
break;
case Browser::TYPE_DEVTOOLS:
window_type = WindowMetricsEvent::TYPE_APP;
break;
default:
NOTREACHED();
}
WindowMetricsEvent::ShowState show_state =
WindowMetricsEvent::SHOW_STATE_UNKNOWN;
if (browser->window()->IsFullscreen())
show_state = WindowMetricsEvent::SHOW_STATE_FULLSCREEN;
else if (browser->window()->IsMinimized())
show_state = WindowMetricsEvent::SHOW_STATE_MINIMIZED;
else if (browser->window()->IsMaximized())
show_state = WindowMetricsEvent::SHOW_STATE_MAXIMIZED;
else
show_state = WindowMetricsEvent::SHOW_STATE_NORMAL;
const bool is_active = browser->window()->IsActive();
const int tab_count = browser->tab_strip_model()->count();
return {window_type, show_state, is_active, tab_count};
}