blob: c583b6ceb7290ff47d0cfd0c9074df8291171d6a [file] [log] [blame]
// Copyright 2024 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/web_applications/sampling_metrics_provider.h"
#include "base/containers/contains.h"
#include "base/metrics/histogram_macros.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface_iterator.h"
#include "chrome/browser/web_applications/daily_metrics_helper.h"
#include "chrome/browser/web_applications/mojom/user_display_mode.mojom.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/browser/web_applications/web_app_tab_helper.h"
#include "chrome/browser/web_applications/web_app_utils.h"
#include "components/services/app_service/public/cpp/preferred_apps_list.h"
#include "components/tabs/public/tab_interface.h"
#include "components/webapps/browser/banners/app_banner_manager.h"
#include "components/webapps/browser/banners/installable_web_app_check_result.h"
#include "components/webapps/browser/banners/web_app_banner_data.h"
namespace web_app {
namespace {
// Decreasing this number will improve accuracy at the expense of more frequent
// client-side work.
constexpr int kTimerIntervalInSeconds = 5 * 60;
using IdSet = std::set<webapps::AppId>;
// Emits UKM metrics for the given tab. The tab may be for an app window or a
// normal browser window.
void EmitUkmMetricsForTab(tabs::TabInterface* tab) {
BrowserWindowInterface* browser = tab->GetBrowserWindowInterface();
CHECK(browser);
Profile* profile = browser->GetProfile();
auto* web_app_helper =
web_app::WebAppTabHelper::FromWebContents(tab->GetContents());
CHECK(web_app_helper);
std::optional<webapps::AppId> app_id = web_app_helper->app_id();
CHECK(app_id);
auto* provider = web_app::WebAppProvider::GetForWebApps(profile);
auto& registrar = provider->registrar_unsafe();
DailyInteraction interaction;
interaction.start_url = registrar.GetAppStartUrl(*app_id);
interaction.installed = registrar.IsInstallState(
*app_id, {proto::INSTALLED_WITHOUT_OS_INTEGRATION,
proto::INSTALLED_WITH_OS_INTEGRATION});
auto install_source =
provider->registrar_unsafe().GetLatestAppInstallSource(*app_id);
if (install_source) {
interaction.install_source = static_cast<int>(*install_source);
}
DisplayMode display_mode =
provider->registrar_unsafe().GetAppEffectiveDisplayMode(*app_id);
interaction.effective_display_mode = static_cast<int>(display_mode);
#if BUILDFLAG(IS_CHROMEOS)
if (apps::AppServiceProxyFactory::IsAppServiceAvailableForProfile(profile)) {
auto* proxy = apps::AppServiceProxyFactory::GetForProfile(profile);
interaction.captures_links =
proxy->PreferredAppsList().IsPreferredAppForSupportedLinks(*app_id);
}
#else
interaction.captures_links = registrar.CapturesLinksInScope(*app_id);
#endif
interaction.promotable = !registrar.IsDiyApp(*app_id);
if (tab->IsActivated() && browser->IsActive()) {
interaction.foreground_duration = base::Seconds(kTimerIntervalInSeconds);
} else {
interaction.background_duration = base::Seconds(kTimerIntervalInSeconds);
}
// Note that with this new sampling approach we are no longer tracking the
// concept of session, and thus don't fill in num_sessions.
FlushOldRecordsAndUpdate(interaction, profile);
}
// Checks whether metrics should be emitted. If so, updates `emitted_ids` and
// emits metrics.
void MaybeEmitUkmMetricsForTab(tabs::TabInterface* tab, IdSet& emitted_ids) {
CHECK(tab->GetContents());
auto* web_app_helper =
web_app::WebAppTabHelper::FromWebContents(tab->GetContents());
if (!web_app_helper) {
return;
}
std::optional<webapps::AppId> app_id = web_app_helper->app_id();
// A tab in an app window doesn't necessarily have to be in-scope of that
// app. In can be out of scope, or simply not have it's navigation committed
// yet.
if (!app_id) {
return;
}
// We only emit UKM metrics a single time for a given AppId.
if (base::Contains(emitted_ids, *app_id)) {
return;
}
auto* provider = web_app::WebAppProvider::GetForWebApps(
tab->GetBrowserWindowInterface()->GetProfile());
auto& registrar = provider->registrar_unsafe();
std::optional<mojom::UserDisplayMode> user_display_mode =
registrar.GetAppUserDisplayMode(*app_id);
CHECK(user_display_mode);
switch (*user_display_mode) {
case mojom::UserDisplayMode::kBrowser:
// Emit metrics only if the app is configured to run in a tab
// window, and is running in a tab.
if (tab->GetBrowserWindowInterface()->GetType() ==
BrowserWindowInterface::Type::TYPE_NORMAL) {
emitted_ids.insert(*app_id);
EmitUkmMetricsForTab(tab);
}
break;
case mojom::UserDisplayMode::kStandalone:
// Emit metrics only if the app is configured to run in a standalone
// window, and is running in a standalone window.
if (tab->GetBrowserWindowInterface()->GetAppBrowserController()) {
emitted_ids.insert(*app_id);
EmitUkmMetricsForTab(tab);
}
break;
case mojom::UserDisplayMode::kTabbed:
// We don't track metrics for tabbed standalone PWAs.
break;
}
}
// Checks whether metrics should be emitted. If so, updates `emitted_ids` and
// emits metrics.
void MaybeEmitUkmMetricsForPromotable(
tabs::TabInterface* tab,
const webapps::WebAppBannerData& banner_data,
IdSet& emitted_ids) {
const GURL& url = banner_data.manifest().start_url;
if (base::Contains(emitted_ids, url.spec())) {
return;
}
emitted_ids.insert(url.spec());
DailyInteraction interaction;
interaction.start_url = url;
interaction.installed = false;
if (!banner_data.manifest().display_override.empty()) {
interaction.effective_display_mode =
static_cast<int>(banner_data.manifest().display_override[0]);
} else {
interaction.effective_display_mode =
static_cast<int>(banner_data.manifest().display);
}
interaction.promotable = true;
FlushOldRecordsAndUpdate(interaction,
tab->GetBrowserWindowInterface()->GetProfile());
}
} // namespace
SamplingMetricsProvider::SamplingMetricsProvider() {
timer_.Start(FROM_HERE, base::Seconds(kTimerIntervalInSeconds),
base::BindRepeating(&SamplingMetricsProvider::EmitMetrics));
}
SamplingMetricsProvider::~SamplingMetricsProvider() = default;
void SamplingMetricsProvider::EmitMetrics() {
// Total number of PWAs, including backgrounded, separated by tabbed vs
// standalone.
int standalone_pwas_count = 0;
int tabbed_pwas_count = 0;
// Whether the foreground window has a PWA as its foreground tab, separated by
// tabbed vs standalone.
bool standalone_pwas_in_active_use = false;
bool tabbed_pwas_in_active_use = false;
// Number of tabbed PWAs, split by most common configurations.
int tabbed_pwas_user_display_mode_browser_count = 0;
int tabbed_pwas_user_display_mode_browser_installed_by_user_count = 0;
int tabbed_pwas_display_mode_standalone_count = 0;
int tabbed_pwas_display_mode_standalone_installed_by_user_count = 0;
IdSet emitted_ukm_ids;
for (BrowserWindowInterface* browser : GetAllBrowserWindowInterfaces()) {
if (!AreWebAppsEnabled(browser->GetProfile())) {
continue;
}
// If this is a standalone app window.
if (browser->GetAppBrowserController()) {
// A browser may be being closed due to empty tabs. See
// https://crbug.com/378020140.
if (!browser->GetActiveTabInterface()) {
continue;
}
++standalone_pwas_count;
// TODO(https://crbug.com/358404364): This function does not work on macOS
// for app windows.
if (browser->IsActive()) {
standalone_pwas_in_active_use = true;
}
MaybeEmitUkmMetricsForTab(browser->GetActiveTabInterface(),
emitted_ukm_ids);
}
// If this is a PWA-tab in a normal browser window.
if (browser->GetType() == BrowserWindowInterface::Type::TYPE_NORMAL) {
auto* provider =
web_app::WebAppProvider::GetForWebApps(browser->GetProfile());
auto& registrar = provider->registrar_unsafe();
for (tabs::TabInterface* tab : browser->GetAllTabInterfaces()) {
auto* web_app_helper =
web_app::WebAppTabHelper::FromWebContents(tab->GetContents());
std::optional<webapps::AppId> app_id = web_app_helper->app_id();
if (app_id) {
++tabbed_pwas_count;
if (tab->IsActivated() && browser->IsActive()) {
tabbed_pwas_in_active_use = true;
}
std::optional<mojom::UserDisplayMode> user_display_mode =
registrar.GetAppUserDisplayMode(*app_id);
bool installed_by_user = registrar.WasInstalledByUser(*app_id);
if (user_display_mode == mojom::UserDisplayMode::kBrowser) {
++tabbed_pwas_user_display_mode_browser_count;
if (installed_by_user) {
++tabbed_pwas_user_display_mode_browser_installed_by_user_count;
}
}
if (user_display_mode == mojom::UserDisplayMode::kStandalone) {
++tabbed_pwas_display_mode_standalone_count;
if (installed_by_user) {
++tabbed_pwas_display_mode_standalone_installed_by_user_count;
}
}
MaybeEmitUkmMetricsForTab(tab, emitted_ukm_ids);
} else {
// If the tab does not have an app id, it might be promotable.
auto* app_banner_manager =
webapps::AppBannerManager::FromWebContents(tab->GetContents());
if (app_banner_manager) {
std::optional<webapps::WebAppBannerData> banner_data =
app_banner_manager->GetCurrentWebAppBannerData();
webapps::InstallableWebAppCheckResult installable =
app_banner_manager->GetInstallableWebAppCheckResult();
if (banner_data &&
installable ==
webapps::InstallableWebAppCheckResult::kYes_Promotable) {
MaybeEmitUkmMetricsForPromotable(tab, *banner_data,
emitted_ukm_ids);
}
}
}
}
}
}
int pwas_count = standalone_pwas_count + tabbed_pwas_count;
bool pwas_in_active_use =
standalone_pwas_in_active_use || tabbed_pwas_in_active_use;
UMA_HISTOGRAM_COUNTS_100("WebApp.Engagement2.Count", pwas_count);
UMA_HISTOGRAM_BOOLEAN("WebApp.Engagement2.Active", pwas_in_active_use);
UMA_HISTOGRAM_COUNTS_100("WebApp.Engagement2.Standalone.Count",
standalone_pwas_count);
UMA_HISTOGRAM_COUNTS_100("WebApp.Engagement2.Tabbed.Count",
tabbed_pwas_count);
UMA_HISTOGRAM_BOOLEAN("WebApp.Engagement2.Standalone.Active",
standalone_pwas_in_active_use);
UMA_HISTOGRAM_BOOLEAN("WebApp.Engagement2.Tabbed.Active",
tabbed_pwas_in_active_use);
UMA_HISTOGRAM_COUNTS_100(
"WebApp.Engagement2.Tabbed.UserDisplayModeBrowser.Count",
tabbed_pwas_user_display_mode_browser_count);
UMA_HISTOGRAM_COUNTS_100(
"WebApp.Engagement2.Tabbed.UserDisplayModeBrowserInstalledByUser.Count",
tabbed_pwas_user_display_mode_browser_installed_by_user_count);
UMA_HISTOGRAM_COUNTS_100(
"WebApp.Engagement2.Tabbed.UserDisplayModeStandalone.Count",
tabbed_pwas_display_mode_standalone_count);
UMA_HISTOGRAM_COUNTS_100(
"WebApp.Engagement2.Tabbed.UserDisplayModeStandaloneInstalledByUser."
"Count",
tabbed_pwas_display_mode_standalone_installed_by_user_count);
}
} // namespace web_app