blob: 263da194fd0b93c17fc8c598df07802a56b493b2 [file] [log] [blame]
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/sessions/session_restore_stats_collector.h"
#include <string>
#include <utility>
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/strcat.h"
#include "base/task/bind_post_task.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/resource_coordinator/session_restore_policy.h"
#include "components/performance_manager/public/decorators/site_data_recorder.h"
#include "components/performance_manager/public/features.h"
#include "components/performance_manager/public/graph/page_node.h"
#include "components/performance_manager/public/performance_manager.h"
#include "components/performance_manager/public/persistence/site_data/feature_usage.h"
#include "components/performance_manager/public/persistence/site_data/site_data_reader.h"
#include "components/site_engagement/content/site_engagement_service.h"
#include "components/site_engagement/core/mojom/site_engagement_details.mojom.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/permission_controller.h"
#include "content/public/browser/permission_descriptor_util.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "third_party/blink/public/common/permissions/permission_utils.h"
#include "third_party/blink/public/mojom/permissions/permission_status.mojom.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace {
using content::RenderWidgetHost;
using content::RenderWidgetHostView;
using content::WebContents;
using performance_manager::PerformanceManager;
using performance_manager::SiteDataReader;
using performance_manager::SiteFeatureUsage;
SessionRestoreStatsCollector* g_instance = nullptr;
// Returns the RenderWidgetHost associated with a WebContents.
RenderWidgetHost* GetRenderWidgetHost(WebContents* web_contents) {
content::RenderWidgetHostView* render_widget_host_view =
web_contents->GetRenderWidgetHostView();
if (render_widget_host_view)
return render_widget_host_view->GetRenderWidgetHost();
return nullptr;
}
// Returns the site engagement score of a WebContents. Copied from
// chrome/browser/resource_coordinator/session_restore_policy.cc.
size_t GetSiteEngagementScore(content::WebContents* contents) {
// Get the active navigation entry. Restored tabs should always have one.
auto& controller = contents->GetController();
auto* nav_entry =
controller.GetEntryAtIndex(controller.GetCurrentEntryIndex());
DCHECK(nav_entry);
auto* engagement_svc = site_engagement::SiteEngagementService::Get(
Profile::FromBrowserContext(contents->GetBrowserContext()));
double engagement =
engagement_svc->GetDetails(nav_entry->GetURL()).total_score;
// Return the engagement as an integer.
return engagement;
}
bool HasLowSiteEngagement(content::WebContents* contents) {
// There are 3 ways to handle session restore:
//
// Case 1: kBackgroundTabLoadingFromPerformanceManager is disabled.
//
// The algorithm uses SessionRestorePolicy::kMinSiteEngagementToRestore.
// SessionRestore.TabCount.LowSiteEngagement counts tabs that are less than
// this.
//
// Case 2: kBackgroundTabLoadingFromPerformanceManager is enabled, and uses
// a min value from kBackgroundTabLoadingMinSiteEngagement.
//
// SessionRestore.TabCount.LowSiteEngagement counts tabs that are less than
// kBackgroundTabLoadingMinSiteEngagement.
//
// Case 3: kBackgroundTabLoadingFromPerformanceManager is enabled, but
// kBackgroundTabLoadingMinSiteEngagement returns 0.
//
// The session restore algorithm ignores site engagement.
// SessionRestore.TabCount.LowSiteEngagement counts tabs that are less than
// SessionRestorePolicy::kMinSiteEngagementToRestore, to see what data the PM
// algorithm is ignoring.
const size_t min_site_engagement =
performance_manager::features::kBackgroundTabLoadingMinSiteEngagement
.Get();
if (min_site_engagement == 0) {
return GetSiteEngagementScore(contents) <
resource_coordinator::SessionRestorePolicy::
kMinSiteEngagementToRestore;
}
return GetSiteEngagementScore(contents) < min_site_engagement;
}
bool HasNotificationPermission(content::WebContents* contents) {
return contents->GetBrowserContext()
->GetPermissionController()
->GetPermissionResultForOriginWithoutContext(
content::PermissionDescriptorUtil::
CreatePermissionDescriptorForPermissionType(
blink::PermissionType::NOTIFICATIONS),
url::Origin::Create(contents->GetLastCommittedURL()))
.status == blink::mojom::PermissionStatus::GRANTED;
}
void LogFirstPaintHistogram(base::TimeDelta paint_time,
std::string_view suffix = "") {
base::UmaHistogramCustomTimes(
base::StrCat({"SessionRestore.ForegroundTabFirstPaint4", suffix}),
paint_time, base::Milliseconds(100), base::Minutes(16), 50);
}
} // namespace
SessionRestoreStatsCollector::TabLoaderStats::TabLoaderStats()
: tab_count(0u),
tab_first_paint_reason(PAINT_FINISHED_UMA_MAX) {}
SessionRestoreStatsCollector::SessionRestoreStatsCollector(
const base::TimeTicks& restore_started,
std::unique_ptr<StatsReportingDelegate> reporting_delegate)
: non_restored_tab_painted_first_(false),
hidden_or_occluded_tab_ignored_(false),
restore_started_(restore_started),
reporting_delegate_(std::move(reporting_delegate)) {
DCHECK(!g_instance);
g_instance = this;
}
SessionRestoreStatsCollector::~SessionRestoreStatsCollector() {
DCHECK_EQ(g_instance, this);
g_instance = nullptr;
}
// static
SessionRestoreStatsCollector* SessionRestoreStatsCollector::GetOrCreateInstance(
base::TimeTicks restore_started,
std::unique_ptr<StatsReportingDelegate> reporting_delegate) {
if (g_instance)
return g_instance;
return new SessionRestoreStatsCollector(restore_started,
std::move(reporting_delegate));
}
void SessionRestoreStatsCollector::TrackTabs(
const std::vector<SessionRestoreDelegate::RestoredTab>& tabs) {
tab_loader_stats_.tab_count += tabs.size();
for (const auto& tab : tabs) {
RegisterObserverForTab(tab.contents());
if (tab.is_active()) {
tab_loader_stats_.active_tab_count++;
}
if (tab.is_app()) {
tab_loader_stats_.app_tab_count++;
}
if (tab.is_internal_page()) {
tab_loader_stats_.internal_page_tab_count++;
}
if (tab.is_pinned()) {
tab_loader_stats_.pinned_tab_count++;
}
if (tab.group().has_value()) {
tab_loader_stats_.grouped_tab_count++;
}
// Look up background feature use in `site_data_reader` on the PM sequence,
// and post the result to OnTabUpdatesInBackground() on the current
// sequence, along with site engagement and permissions info.
base::OnceCallback<bool(const SiteDataReader&)> on_site_data_ready =
base::BindOnce([](const SiteDataReader& site_data_reader) {
return site_data_reader.UpdatesFaviconInBackground() ==
SiteFeatureUsage::kSiteFeatureInUse ||
site_data_reader.UpdatesTitleInBackground() ==
SiteFeatureUsage::kSiteFeatureInUse;
});
base::OnceCallback<void(bool)> on_tab_updates_in_background =
base::BindOnce(&SessionRestoreStatsCollector::OnTabUpdatesInBackground,
weak_factory_.GetWeakPtr(),
HasLowSiteEngagement(tab.contents()),
HasNotificationPermission(tab.contents()));
performance_manager::WaitForSiteDataReader(
PerformanceManager::GetPrimaryPageNodeForWebContents(tab.contents()),
std::move(on_site_data_ready)
.Then(std::move(on_tab_updates_in_background)));
}
// If we were not able to register observers for any tab, report stats.
if (tracked_tabs_occluded_map_.empty())
ReportStatsAndSelfDestroy();
}
void SessionRestoreStatsCollector::RenderWidgetHostVisibilityChanged(
content::RenderWidgetHost* widget_host,
bool became_visible) {
if (became_visible)
return;
auto host_and_occluded_it = tracked_tabs_occluded_map_.find(widget_host);
if (host_and_occluded_it == tracked_tabs_occluded_map_.end())
return;
host_and_occluded_it->second = true;
}
void SessionRestoreStatsCollector::RenderWidgetHostDidUpdateVisualProperties(
content::RenderWidgetHost* widget_host) {
// Only process this event if first paint hasn't been seen and this is a
// paint of a tab that has not been hidden or occluded.
if (!widget_host->GetView() || !widget_host->GetView()->IsShowing())
return;
auto host_and_occluded_it = tracked_tabs_occluded_map_.find(widget_host);
if (host_and_occluded_it != tracked_tabs_occluded_map_.end()) {
// Ignore first paint of a restored tab that was hidden or occluded
// before first paint. If another restored tab is painted, its paint
// time will be recorded.
if (host_and_occluded_it->second) {
hidden_or_occluded_tab_ignored_ = true;
return;
}
// This is a paint for a tab that is explicitly being tracked so
// update the statistics. Otherwise the host is for a tab that's not
// being tracked. Thus some other tab has visibility and has rendered
// and there's no point in tracking the time to first paint. This can
// happen because the user opened a different tab or restored tabs
// to an already existing browser and an existing tab was in the
// foreground.
base::TimeDelta time_to_paint = base::TimeTicks::Now() - restore_started_;
tab_loader_stats_.foreground_tab_first_paint = time_to_paint;
} else {
non_restored_tab_painted_first_ = true;
}
ReportStatsAndSelfDestroy();
}
void SessionRestoreStatsCollector::RenderWidgetHostDestroyed(
content::RenderWidgetHost* widget_host) {
render_widget_host_observations_.RemoveObservation(widget_host);
tracked_tabs_occluded_map_.erase(widget_host);
if (tracked_tabs_occluded_map_.empty())
ReportStatsAndSelfDestroy();
}
void SessionRestoreStatsCollector::RegisterObserverForTab(WebContents* tab) {
content::RenderWidgetHost* rwh = GetRenderWidgetHost(tab);
// If we don't have a RenderWidgetHost, we can't track paints for the tab so
// ignore it.
if (!rwh)
return;
// Assume that tabs do not start out hidden or occluded.
auto result = tracked_tabs_occluded_map_.emplace(rwh, false);
DCHECK(result.second);
render_widget_host_observations_.AddObservation(rwh);
}
void SessionRestoreStatsCollector::ReportStatsAndSelfDestroy() {
if (!tab_loader_stats_.foreground_tab_first_paint.is_zero()) {
tab_loader_stats_.tab_first_paint_reason = PAINT_FINISHED_UMA_DONE;
} else if (non_restored_tab_painted_first_) {
tab_loader_stats_.tab_first_paint_reason =
PAINT_FINISHED_NON_RESTORED_TAB_PAINTED_FIRST;
} else if (hidden_or_occluded_tab_ignored_) {
tab_loader_stats_.tab_first_paint_reason =
PAINT_FINISHED_UMA_NO_COMPLETELY_VISIBLE_TABS;
} else {
tab_loader_stats_.tab_first_paint_reason = PAINT_FINISHED_UMA_NO_PAINT;
}
reporting_delegate_->ReportTabLoaderStats(tab_loader_stats_);
delete this;
}
void SessionRestoreStatsCollector::OnTabUpdatesInBackground(
bool low_site_engagement,
bool background_notification_permission,
bool updates_in_background) {
if (background_notification_permission) {
tab_loader_stats_.notification_permission_tab_count++;
}
if (updates_in_background) {
tab_loader_stats_.updates_in_background_tab_count++;
}
if (low_site_engagement) {
tab_loader_stats_.low_site_engagement_tab_count++;
if (background_notification_permission || updates_in_background) {
tab_loader_stats_
.low_site_engagement_with_background_communication_tab_count++;
}
}
}
SessionRestoreStatsCollector::UmaStatsReportingDelegate::
UmaStatsReportingDelegate() = default;
void SessionRestoreStatsCollector::UmaStatsReportingDelegate::
ReportTabLoaderStats(const TabLoaderStats& tab_loader_stats) {
if (!tab_loader_stats.foreground_tab_first_paint.is_zero()) {
LogFirstPaintHistogram(tab_loader_stats.foreground_tab_first_paint);
std::string_view count_suffix;
CHECK_GT(tab_loader_stats.tab_count, 0u);
if (tab_loader_stats.tab_count < 2) {
count_suffix = ".1Tab";
} else if (tab_loader_stats.tab_count < 4) {
count_suffix = ".2to3Tabs";
} else if (tab_loader_stats.tab_count < 8) {
count_suffix = ".4to7Tabs";
} else if (tab_loader_stats.tab_count < 16) {
count_suffix = ".8to15Tabs";
} else if (tab_loader_stats.tab_count < 32) {
count_suffix = ".16to31Tabs";
} else {
count_suffix = ".32PlusTabs";
}
LogFirstPaintHistogram(tab_loader_stats.foreground_tab_first_paint,
count_suffix);
}
base::UmaHistogramEnumeration(
"SessionRestore.ForegroundTabFirstPaint4.FinishReason",
tab_loader_stats.tab_first_paint_reason, PAINT_FINISHED_UMA_MAX);
base::UmaHistogramCounts100("SessionRestore.TabCount",
tab_loader_stats.tab_count);
base::UmaHistogramCounts100("SessionRestore.TabCount.Active",
tab_loader_stats.active_tab_count);
base::UmaHistogramCounts100("SessionRestore.TabCount.App",
tab_loader_stats.app_tab_count);
base::UmaHistogramCounts100("SessionRestore.TabCount.InternalPage",
tab_loader_stats.internal_page_tab_count);
base::UmaHistogramCounts100("SessionRestore.TabCount.Pinned",
tab_loader_stats.pinned_tab_count);
base::UmaHistogramCounts100("SessionRestore.TabCount.Grouped",
tab_loader_stats.grouped_tab_count);
base::UmaHistogramCounts100("SessionRestore.TabCount.LowSiteEngagement",
tab_loader_stats.low_site_engagement_tab_count);
base::UmaHistogramCounts100(
"SessionRestore.TabCount.LowSiteEngagementWithBackgroundCommunication",
tab_loader_stats
.low_site_engagement_with_background_communication_tab_count);
base::UmaHistogramCounts100(
"SessionRestore.TabCount.BackgroundNotificationPermission",
tab_loader_stats.notification_permission_tab_count);
base::UmaHistogramCounts100(
"SessionRestore.TabCount.UpdatesTitleOrFaviconInBackground",
tab_loader_stats.updates_in_background_tab_count);
}