blob: e2bde5634dc2cb7b7f5812ba39197fdfc0d6d814 [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/ui/tabs/window_activity_watcher.h"
#include "base/optional.h"
#include "base/scoped_observer.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/tabs/tab_metrics_event.pb.h"
#include "services/metrics/public/cpp/metrics_utils.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
using metrics::WindowMetricsEvent;
namespace {
struct WindowMetrics {
bool operator==(const WindowMetrics& other) {
return window_id == other.window_id && type == other.type &&
show_state == other.show_state && is_active == other.is_active &&
tab_count == other.tab_count;
}
bool operator!=(const WindowMetrics& other) { return !operator==(other); }
// ID for the window, unique within the Chrome session.
SessionID::id_type window_id;
WindowMetricsEvent::Type type;
// TODO(michaelpg): Observe the show state and log when it changes.
WindowMetricsEvent::ShowState show_state;
// True if this is the active (frontmost) window.
bool is_active;
// Number of tabs in the tab strip.
int tab_count;
};
// Sets metric values that are dependent on the current window state.
void UpdateMetrics(const Browser* browser, WindowMetrics* window_metrics) {
if (browser->window()->IsFullscreen())
window_metrics->show_state = WindowMetricsEvent::SHOW_STATE_FULLSCREEN;
else if (browser->window()->IsMinimized())
window_metrics->show_state = WindowMetricsEvent::SHOW_STATE_MINIMIZED;
else if (browser->window()->IsMaximized())
window_metrics->show_state = WindowMetricsEvent::SHOW_STATE_MAXIMIZED;
else
window_metrics->show_state = WindowMetricsEvent::SHOW_STATE_NORMAL;
window_metrics->is_active = browser->window()->IsActive();
window_metrics->tab_count = browser->tab_strip_model()->count();
}
// Returns a populated WindowMetrics for the browser.
WindowMetrics CreateMetrics(const Browser* browser) {
WindowMetrics window_metrics;
window_metrics.window_id = browser->session_id().id();
switch (browser->type()) {
case Browser::TYPE_TABBED:
window_metrics.type = WindowMetricsEvent::TYPE_TABBED;
break;
case Browser::TYPE_POPUP:
window_metrics.type = WindowMetricsEvent::TYPE_POPUP;
break;
}
UpdateMetrics(browser, &window_metrics);
return window_metrics;
}
// Logs a UKM entry with the metrics from |window_metrics|.
void LogWindowMetricsUkmEntry(const WindowMetrics& window_metrics) {
ukm::UkmRecorder* ukm_recorder = ukm::UkmRecorder::Get();
if (!ukm_recorder)
return;
ukm::builders::TabManager_WindowMetrics entry(ukm::AssignNewSourceId());
entry.SetWindowId(window_metrics.window_id)
.SetIsActive(window_metrics.is_active)
.SetShowState(window_metrics.show_state)
.SetType(window_metrics.type);
// Bucketize values for privacy considerations. Use a spacing factor that
// ensures values up to 3 can be logged exactly; after that, the precise
// number becomes less significant.
int tab_count = window_metrics.tab_count;
if (tab_count > 3)
tab_count = ukm::GetExponentialBucketMin(tab_count, 1.5);
entry.SetTabCount(tab_count);
entry.Record(ukm_recorder);
}
} // namespace
// Observes a browser window's tab strip and logs a WindowMetrics UKM event for
// the window upon changes to metrics like TabCount.
class WindowActivityWatcher::BrowserWatcher : public TabStripModelObserver {
public:
explicit BrowserWatcher(Browser* browser)
: browser_(browser), observer_(this) {
DCHECK(!browser->profile()->IsOffTheRecord());
MaybeLogWindowMetricsUkmEntry();
observer_.Add(browser->tab_strip_model());
}
~BrowserWatcher() override = default;
// Logs a new WindowMetrics entry to the UKM recorder if the entry would be
// different than the last one we logged.
void MaybeLogWindowMetricsUkmEntry() {
// Do nothing if the window has no tabs (which can happen when a window is
// opened, before a tab is added) or is being closed.
if (browser_->tab_strip_model()->empty() ||
browser_->tab_strip_model()->closing_all()) {
return;
}
if (!last_window_metrics_) {
last_window_metrics_ = CreateMetrics(browser_);
LogWindowMetricsUkmEntry(last_window_metrics_.value());
return;
}
// Copy old state to compare with.
WindowMetrics old_metrics = last_window_metrics_.value();
UpdateMetrics(browser_, &last_window_metrics_.value());
// We only need to create a new UKM entry if the metrics have changed.
if (old_metrics != last_window_metrics_.value())
LogWindowMetricsUkmEntry(last_window_metrics_.value());
}
private:
// TabStripModelObserver:
void TabInsertedAt(TabStripModel* tab_strip_model,
content::WebContents* contents,
int index,
bool foreground) override {
MaybeLogWindowMetricsUkmEntry();
}
void TabDetachedAt(content::WebContents* contents, int index) override {
MaybeLogWindowMetricsUkmEntry();
}
// The browser whose tab strip we track. WindowActivityWatcher should ensure
// this outlives us.
Browser* browser_;
// The most recent WindowMetrics entry logged. We only log a new UKM entry if
// some metric value has changed.
base::Optional<WindowMetrics> last_window_metrics_;
// Used to update the tab count for browser windows.
ScopedObserver<TabStripModel, TabStripModelObserver> observer_;
DISALLOW_COPY_AND_ASSIGN(BrowserWatcher);
};
// static
WindowActivityWatcher* WindowActivityWatcher::GetInstance() {
CR_DEFINE_STATIC_LOCAL(WindowActivityWatcher, instance, ());
return &instance;
}
WindowActivityWatcher::WindowActivityWatcher() {
BrowserList::AddObserver(this);
for (Browser* browser : *BrowserList::GetInstance())
OnBrowserAdded(browser);
}
WindowActivityWatcher::~WindowActivityWatcher() {
BrowserList::RemoveObserver(this);
}
bool WindowActivityWatcher::ShouldTrackBrowser(Browser* browser) {
// Don't track incognito browsers. This is also enforced by UKM.
return !browser->profile()->IsOffTheRecord();
}
void WindowActivityWatcher::OnBrowserAdded(Browser* browser) {
if (ShouldTrackBrowser(browser))
browser_watchers_[browser] = std::make_unique<BrowserWatcher>(browser);
}
void WindowActivityWatcher::OnBrowserRemoved(Browser* browser) {
browser_watchers_.erase(browser);
}
void WindowActivityWatcher::OnBrowserSetLastActive(Browser* browser) {
if (ShouldTrackBrowser(browser))
browser_watchers_[browser]->MaybeLogWindowMetricsUkmEntry();
}
void WindowActivityWatcher::OnBrowserNoLongerActive(Browser* browser) {
if (ShouldTrackBrowser(browser))
browser_watchers_[browser]->MaybeLogWindowMetricsUkmEntry();
}