blob: 15b4907f3cec547313b20a9ff252236a31336f9e [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_METRICS_TAB_STATS_TAB_STATS_TRACKER_H_
#define CHROME_BROWSER_METRICS_TAB_STATS_TAB_STATS_TRACKER_H_
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "base/functional/function_ref.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/observer_list.h"
#include "base/power_monitor/power_observer.h"
#include "base/sequence_checker.h"
#include "base/timer/timer.h"
#include "build/build_config.h"
#include "chrome/browser/metrics/tab_stats/tab_stats_data_store.h"
#include "components/metrics/daily_event.h"
#include "content/public/browser/web_contents_observer.h"
#if !BUILDFLAG(IS_ANDROID)
#include "chrome/browser/resource_coordinator/lifecycle_unit_observer.h"
#endif
class PrefRegistrySimple;
class PrefService;
class Profile;
#if BUILDFLAG(IS_ANDROID)
class TabModel;
#else
class BrowserWindowInterface;
#endif
namespace content {
class WebContents;
}
namespace metrics {
FORWARD_DECLARE_TEST(TabStatsTrackerBrowserTest,
TabDeletionGetsHandledProperly);
// Class for tracking and recording the tabs and browser windows usage.
//
// This class is meant to be used as a singleton by calling the SetInstance
// method, e.g.:
// TabStatsTracker::SetInstance(
// std::make_unique<TabStatsTracker>(g_browser_process->local_state()));
class TabStatsTracker :
#if !BUILDFLAG(IS_ANDROID)
public resource_coordinator::LifecycleUnitObserver,
#endif
public base::PowerSuspendObserver {
public:
// Abstraction of a Browser + TabStripModel (on desktop) or a TabModel (on
// Android).
class TabStripInterface;
// Constructor. |pref_service| must outlive this object.
explicit TabStatsTracker(PrefService* pref_service);
TabStatsTracker(const TabStatsTracker&) = delete;
TabStatsTracker& operator=(const TabStatsTracker&) = delete;
~TabStatsTracker() override;
// Sets the |TabStatsTracker| global instance.
static void SetInstance(std::unique_ptr<TabStatsTracker> instance);
// Clears and tears down the |TabStatsTracker| global instance.
static void ClearInstance();
// Returns the |TabStatsTracker| global instance. CHECKs there is an instance.
static TabStatsTracker* GetInstance();
// Returns whether there is a global instance.
static bool HasInstance();
// Registers a TabStatsObserver instance. Upon registering the initial state
// of the observer is made to match the current browser/tab state.
void AddObserverAndSetInitialState(TabStatsObserver* observer);
void RemoveObserver(TabStatsObserver* observer) {
tab_stats_observers_.RemoveObserver(observer);
}
// Registers prefs used to track tab stats.
static void RegisterPrefs(PrefRegistrySimple* registry);
// Accessors.
const TabStatsDataStore::TabsStats& tab_stats() const;
content::WebContentsObserver* GetWebContentsUsageObserverForTesting(
content::WebContents* web_contents);
protected:
FRIEND_TEST_ALL_PREFIXES(TabStatsTrackerBrowserTest,
TabDeletionGetsHandledProperly);
FRIEND_TEST_ALL_PREFIXES(TabStatsTrackerBrowserTest,
TabsAndWindowsAreCountedAccurately);
#if BUILDFLAG(IS_WIN)
FRIEND_TEST_ALL_PREFIXES(TabStatsTrackerBrowserTest,
TestCalculateAndRecordNativeWindowVisibilities);
#endif
// The UmaStatsReportingDelegate is responsible for delivering statistics
// reported by the TabStatsTracker via UMA.
class UmaStatsReportingDelegate {
public:
// The name of the histogram that records the number of tabs total at resume
// from sleep/hibernate.
static const char kNumberOfTabsOnResumeHistogramName[];
// The name of the histogram that records the maximum number of tabs opened
// in a day.
static const char kMaxTabsInADayHistogramName[];
// The name of the histogram that records the maximum number of tabs opened
// in the same window in a day.
static const char kMaxTabsPerWindowInADayHistogramName[];
// The name of the histogram that records the maximum number of windows
// opened in a day.
static const char kMaxWindowsInADayHistogramName[];
// The name of the histograms that records the current number of
// tabs/windows.
static const char kTabCountHistogramName[];
static const char kWindowCountHistogramName[];
// The name of the histogram that records each window's width, in DIPs.
static const char kWindowWidthHistogramName[];
// The names of the histograms that record daily discard/reload counts
// caused for each discard reason.
static const char kDailyDiscardsExternalHistogramName[];
static const char kDailyDiscardsUrgentHistogramName[];
static const char kDailyDiscardsProactiveHistogramName[];
static const char kDailyDiscardsSuggestedHistogramName[];
static const char kDailyDiscardsFrozenWithGrowingMemoryHistogramName[];
static const char kDailyReloadsExternalHistogramName[];
static const char kDailyReloadsUrgentHistogramName[];
static const char kDailyReloadsProactiveHistogramName[];
static const char kDailyReloadsSuggestedHistogramName[];
static const char kDailyReloadsFrozenWithGrowingMemoryHistogramName[];
// The names of the histograms that record duplicate tab data.
static const char kTabDuplicateCountSingleWindowHistogramName[];
static const char kTabDuplicateCountAllProfileWindowsHistogramName[];
static const char kTabDuplicatePercentageSingleWindowHistogramName[];
static const char kTabDuplicatePercentageAllProfileWindowsHistogramName[];
static const char
kTabDuplicateExcludingFragmentsCountSingleWindowHistogramName[];
static const char
kTabDuplicateExcludingFragmentsCountAllProfileWindowsHistogramName[];
static const char
kTabDuplicateExcludingFragmentsPercentageSingleWindowHistogramName[];
static const char
kTabDuplicateExcludingFragmentsPercentageAllProfileWindowsHistogramName
[];
UmaStatsReportingDelegate() = default;
UmaStatsReportingDelegate(const UmaStatsReportingDelegate&) = delete;
UmaStatsReportingDelegate& operator=(const UmaStatsReportingDelegate&) =
delete;
virtual ~UmaStatsReportingDelegate() = default;
// Called at resume from sleep/hibernate.
void ReportTabCountOnResume(size_t tab_count);
// Called once per day to report the metrics.
void ReportDailyMetrics(const TabStatsDataStore::TabsStats& tab_stats);
// Report the tab heartbeat metrics.
void ReportHeartbeatMetrics(const TabStatsDataStore::TabsStats& tab_stats);
// Calculate and report the metrics related to tab duplicates, which are
// re-calculated each time rather than cached like the other metrics due to
// their complexity. |exclude_fragments| will treat two tabs with the same
// URL apart from trailing fragments as duplicates, otherwise will only
// treat exact URL matches as duplicates.
void ReportTabDuplicateMetrics(bool exclude_fragments);
protected:
// Checks if Chrome is running in background with no visible windows,
// virtual for unittesting.
virtual bool IsChromeBackgroundedWithoutWindows();
private:
struct DuplicateData {
DuplicateData();
DuplicateData(const DuplicateData&);
~DuplicateData();
int duplicate_count;
int tab_count;
std::set<GURL> seen_urls;
};
};
// The observer that's used by |daily_event_| to report the metrics.
class TabStatsDailyObserver : public DailyEvent::Observer {
public:
// Constructor. |reporting_delegate| and |data_store| must outlive this
// object.
TabStatsDailyObserver(UmaStatsReportingDelegate* reporting_delegate,
TabStatsDataStore* data_store)
: reporting_delegate_(reporting_delegate), data_store_(data_store) {}
TabStatsDailyObserver(const TabStatsDailyObserver&) = delete;
TabStatsDailyObserver& operator=(const TabStatsDailyObserver&) = delete;
~TabStatsDailyObserver() override = default;
// Callback called when the daily event happen.
void OnDailyEvent(DailyEvent::IntervalType type) override;
private:
// The delegate used to report the metrics.
raw_ptr<UmaStatsReportingDelegate, DanglingUntriaged> reporting_delegate_;
// The data store that houses the metrics.
raw_ptr<TabStatsDataStore> data_store_;
};
// Accessors, exposed for unittests:
TabStatsDataStore* tab_stats_data_store() {
return tab_stats_data_store_.get();
}
base::RepeatingTimer* daily_event_timer_for_testing() {
return &daily_event_timer_;
}
DailyEvent* daily_event_for_testing() { return daily_event_.get(); }
UmaStatsReportingDelegate* reporting_delegate_for_testing() {
return reporting_delegate_.get();
}
base::RepeatingTimer* heartbeat_timer_for_testing() {
return &heartbeat_timer_;
}
// Reset the |reporting_delegate_| object to |reporting_delegate|, for testing
// purposes.
void reset_reporting_delegate_for_testing(
UmaStatsReportingDelegate* reporting_delegate) {
reporting_delegate_.reset(reporting_delegate);
}
// Reset the DailyEvent object to |daily_event|, for testing purposes.
void reset_daily_event_for_testing(DailyEvent* daily_event) {
daily_event_.reset(daily_event);
}
void reset_data_store_for_testing(TabStatsDataStore* data_store) {
tab_stats_data_store_.reset(data_store);
}
// base::PowerSuspendObserver:
void OnResume() override;
#if !BUILDFLAG(IS_ANDROID)
// resource_coordinator::LifecycleUnitObserver:
void OnLifecycleUnitStateChanged(
resource_coordinator::LifecycleUnit* lifecycle_unit,
::mojom::LifecycleUnitState previous_state,
::mojom::LifecycleUnitStateChangeReason reason) override;
#endif
// Functions to call when a tab strip (or the Android equivalent) is added,
// removed or modified.
void OnTabStripAdded();
void OnTabStripRemoved();
void OnTabStripNewTabCount(size_t tab_count);
// Functions to call to start tracking a new tab.
void OnInitialOrInsertedTab(content::WebContents* web_contents);
void OnTabReplaced(content::WebContents* old_contents,
content::WebContents* new_contents);
// Functions to call when a WebContents get destroyed.
void OnWebContentsDestroyed(content::WebContents* web_contents);
// Function to call to report the tab heartbeat metrics.
void OnHeartbeatEvent();
private:
// Observer used to be notified when the state of a WebContents changes or
// when it's about to be destroyed.
class WebContentsUsageObserver;
// For access to |tab_stats_observers_|
friend class WebContentsUsageObserver;
// A class that watches for tabs to be added and removed. Abstracts away
// tab strip differences on Android and desktop.
class TabWatcher;
// For access to OnTabStripAdded() and OnTabStripRemoved().
friend class TabWatcher;
// The delegate that reports the events.
std::unique_ptr<UmaStatsReportingDelegate> reporting_delegate_;
// The tab stats.
std::unique_ptr<TabStatsDataStore> tab_stats_data_store_;
// A daily event for collecting metrics once a day.
std::unique_ptr<DailyEvent> daily_event_;
// The list of registered observers.
base::ObserverList<TabStatsObserver> tab_stats_observers_;
// The timer used to periodically check if the daily event should be
// triggered.
base::RepeatingTimer daily_event_timer_;
// The timer used to report the heartbeat metrics at regular interval.
base::RepeatingTimer heartbeat_timer_;
// The observers that track how the tabs are used.
std::map<content::WebContents*, std::unique_ptr<WebContentsUsageObserver>>
web_contents_usage_observers_;
std::unique_ptr<TabWatcher> tab_watcher_;
SEQUENCE_CHECKER(sequence_checker_);
};
// A Browser + TabStripModel (on desktop) or a TabModel (on Android).
// The TabStripInterface must not outlive the underlying model.
class TabStatsTracker::TabStripInterface {
public:
#if BUILDFLAG(IS_ANDROID)
using PlatformModel = TabModel;
const TabModel* tab_model() const { return model_.get(); }
TabModel* tab_model() { return model_.get(); }
#else
using PlatformModel = BrowserWindowInterface;
const BrowserWindowInterface* browser_window_interface() const {
return model_.get();
}
BrowserWindowInterface* browser_window_interface() { return model_.get(); }
#endif
explicit TabStripInterface(PlatformModel* model);
~TabStripInterface();
TabStripInterface(const TabStripInterface&) = delete;
TabStripInterface& operator=(const TabStripInterface&) = delete;
// Calls `func` for each tab in the tab strip that has a non-null
// WebContents. On Android, tabs will be skipped if their WebContents isn't
// initialized yet.
void ForEachWebContents(
base::FunctionRef<void(content::WebContents*)> func) const;
// Returns the count of tabs in this tab strip.
size_t GetTabCount() const;
// Returns the active tab for this tab strip. On Android this may return
// nullptr if the tab's WebContents isn't initialized yet.
content::WebContents* GetActiveWebContents() const;
// Returns the tab at `index` of this tab strip. On Android this may return
// nullptr if the tab's WebContents isn't initialized yet.
content::WebContents* GetWebContentsAt(size_t index) const;
// Returns the profile this tab strip is attached to.
Profile* GetProfile() const;
// Returns true if this tab strip is attached to a TYPE_NORMAL Browser.
// Always returns true on Android.
bool IsInNormalBrowser() const;
// Activates the tab at `index` of this tab strip.
void ActivateTabAtForTesting(size_t index);
// Closes the tab at `index` of this tab strip.
void CloseTabAtForTesting(size_t index);
// Calls `func` for each existing Browser + TabStripModel (or TabModel on
// Android).
static void ForEach(base::FunctionRef<void(const TabStripInterface&)> func);
private:
raw_ptr<PlatformModel> model_;
};
} // namespace metrics
#endif // CHROME_BROWSER_METRICS_TAB_STATS_TAB_STATS_TRACKER_H_