blob: 7915d58e05b59e64bc7b4ed79d9f6d93f85f2782 [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/logging.h"
#include "base/optional.h"
#include "base/scoped_observer.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/resource_coordinator/tab_metrics_event.pb.h"
#include "chrome/browser/resource_coordinator/tab_ranker/window_features.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_window.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"
#if defined(USE_AURA)
#include "ui/aura/window.h"
#endif
using metrics::WindowMetricsEvent;
using tab_ranker::WindowFeatures;
namespace {
// Sets feature values that are dependent on the current window state.
void UpdateWindowFeatures(const Browser* browser,
WindowFeatures* window_features,
bool is_active) {
DCHECK(browser->window());
// TODO(michaelpg): Observe the show state and log when it changes.
if (browser->window()->IsFullscreen())
window_features->show_state = WindowMetricsEvent::SHOW_STATE_FULLSCREEN;
else if (browser->window()->IsMinimized())
window_features->show_state = WindowMetricsEvent::SHOW_STATE_MINIMIZED;
else if (browser->window()->IsMaximized())
window_features->show_state = WindowMetricsEvent::SHOW_STATE_MAXIMIZED;
else
window_features->show_state = WindowMetricsEvent::SHOW_STATE_NORMAL;
window_features->is_active = is_active;
window_features->tab_count = browser->tab_strip_model()->count();
}
// Returns a populated WindowFeatures for the browser.
// |is_active| is provided because IsActive() may be incorrect while browser
// activation is changing (namely, when deactivating a window on Windows).
WindowFeatures CreateWindowFeatures(const Browser* browser, bool is_active) {
WindowMetricsEvent::Type window_type = WindowMetricsEvent::TYPE_UNKNOWN;
switch (browser->type()) {
case Browser::TYPE_TABBED:
window_type = WindowMetricsEvent::TYPE_TABBED;
break;
case Browser::TYPE_POPUP:
window_type = WindowMetricsEvent::TYPE_POPUP;
break;
default:
NOTREACHED();
}
WindowFeatures window_features(browser->session_id(), window_type);
UpdateWindowFeatures(browser, &window_features, is_active);
return window_features;
}
// Logs a UKM entry with the metrics from |window_features|.
void LogWindowMetricsUkmEntry(const WindowFeatures& window_features) {
ukm::UkmRecorder* ukm_recorder = ukm::UkmRecorder::Get();
if (!ukm_recorder)
return;
ukm::builders::TabManager_WindowMetrics entry(ukm::AssignNewSourceId());
entry.SetWindowId(window_features.window_id.id())
.SetIsActive(window_features.is_active)
.SetShowState(window_features.show_state)
.SetType(window_features.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_features.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;
void MaybeLogWindowMetricsUkmEntry() {
MaybeLogWindowMetricsUkmEntry(browser_->window()->IsActive());
}
// Logs a new WindowMetrics entry to the UKM recorder if the entry would be
// different than the last one we logged.
// |is_active| is provided because IsActive() may be incorrect while browser
// activation is changing (namely, when deactivating a window on Windows).
void MaybeLogWindowMetricsUkmEntry(bool is_active) {
// 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_features_) {
last_window_features_.emplace(
::CreateWindowFeatures(browser_, is_active));
LogWindowMetricsUkmEntry(last_window_features_.value());
return;
}
// Copy old state to compare with.
WindowFeatures old_features(last_window_features_.value());
UpdateWindowFeatures(browser_, &last_window_features_.value(), is_active);
// We only need to create a new UKM entry if the metrics have changed.
if (old_features != last_window_features_.value())
LogWindowMetricsUkmEntry(last_window_features_.value());
}
private:
// TabStripModelObserver:
void OnTabStripModelChanged(
TabStripModel* tab_strip_model,
const TabStripModelChange& change,
const TabStripSelectionChange& selection) override {
if (change.type() != TabStripModelChange::kInserted &&
change.type() != TabStripModelChange::kRemoved)
return;
MaybeLogWindowMetricsUkmEntry();
}
// The browser whose tab strip we track. WindowActivityWatcher should ensure
// this outlives us.
Browser* browser_;
// The most recent WindowFeatures entry logged. We only log a new UKM entry if
// some metric value has changed.
base::Optional<WindowFeatures> last_window_features_;
// Used to update the tab count for browser windows.
ScopedObserver<TabStripModel, TabStripModelObserver> observer_;
DISALLOW_COPY_AND_ASSIGN(BrowserWatcher);
};
// static
WindowActivityWatcher* WindowActivityWatcher::GetInstance() {
static base::NoDestructor<WindowActivityWatcher> instance;
return instance.get();
}
// static
WindowFeatures WindowActivityWatcher::CreateWindowFeatures(
const Browser* browser) {
DCHECK(browser->window());
return ::CreateWindowFeatures(browser, browser->window()->IsActive());
}
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) {
// The browser may not have a window yet if activation calls happen during
// initialization.
// TODO(michaelpg): The browser window check should be unnecessary
// (https://crbug.com/811191, https://crbug.com/811243).
if (ShouldTrackBrowser(browser) && browser->window())
browser_watchers_[browser]->MaybeLogWindowMetricsUkmEntry();
}
void WindowActivityWatcher::OnBrowserNoLongerActive(Browser* browser) {
// The browser may not have a window yet if activation calls happen during
// initialization.
// TODO(michaelpg): The browser window check should be unnecessary
// (https://crbug.com/811191, https://crbug.com/811243).
if (!ShouldTrackBrowser(browser) || !browser->window())
return;
#if defined(USE_AURA)
// On some platforms, the window is hidden (and deactivated) before it starts
// closing. Unless the window is minimized, assume that being deactivated
// while hidden means it's about to close, and don't log in that case.
if (browser->window()->GetNativeWindow() &&
!browser->window()->GetNativeWindow()->IsVisible() &&
!browser->window()->IsMinimized()) {
return;
}
#endif
// On Windows, the browser window's IsActive() may still return true until the
// WM updates the focused window, and BrowserList::GetLastActive() still
// returns this browser until another one is activated. So we explicitly pass
// along that the window should be considered inactive.
browser_watchers_[browser]->MaybeLogWindowMetricsUkmEntry(
/*is_active=*/false);
}