blob: 2758c838db1d023239abee7aad49ca96749f43e0 [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 <memory>
#include <vector>
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/strings/stringprintf.h"
#include "chrome/browser/resource_coordinator/tab_activity_watcher.h"
#include "chrome/browser/resource_coordinator/tab_metrics_event.pb.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/tabs/tab_activity_simulator.h"
#include "chrome/browser/ui/tabs/tab_ukm_test_helper.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "chrome/test/base/test_browser_window.h"
#include "chrome/test/base/testing_profile.h"
#include "content/public/test/web_contents_tester.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/mojom/ukm_interface.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/ui_base_types.h"
using metrics::WindowMetricsEvent;
using ukm::builders::TabManager_WindowMetrics;
namespace {
const char* kEntryName = TabManager_WindowMetrics::kEntryName;
const char* kTestUrl = "https://example.com/";
// TestBrowserWindow whose show state can be modified.
class FakeBrowserWindow : public TestBrowserWindow {
public:
FakeBrowserWindow() = default;
~FakeBrowserWindow() override = default;
// Helper function to handle FakeBrowserWindow lifetime. Modeled after
// CreateBrowserWithTestWindowForParams.
static std::unique_ptr<Browser> CreateBrowserWithFakeWindowForParams(
Browser::CreateParams* params) {
// TestBrowserWindowOwner takes ownersip of the window and will destroy the
// window (along with itself) automatically when the browser is closed.
FakeBrowserWindow* window = new FakeBrowserWindow;
new TestBrowserWindowOwner(window);
params->window = window;
auto browser = std::make_unique<Browser>(*params);
window->browser_ = browser.get();
window->Activate();
return browser;
}
// TestBrowserWindow:
void Activate() override {
if (is_active_)
return;
is_active_ = true;
// With a real view, activating would update the BrowserList.
BrowserList::SetLastActive(browser_);
}
void Deactivate() override {
if (!is_active_)
return;
is_active_ = false;
// With a real view, deactivating would notify the BrowserList.
BrowserList::NotifyBrowserNoLongerActive(browser_);
}
bool IsActive() const override { return is_active_; }
bool IsMaximized() const override {
return show_state_ == ui::SHOW_STATE_MAXIMIZED;
}
bool IsMinimized() const override {
return show_state_ == ui::SHOW_STATE_MINIMIZED;
}
void Maximize() override {
show_state_ = ui::SHOW_STATE_MAXIMIZED;
Activate();
}
void Minimize() override {
show_state_ = ui::SHOW_STATE_MINIMIZED;
Deactivate();
}
void Restore() override {
// This isn't true "restore" behavior.
show_state_ = ui::SHOW_STATE_NORMAL;
Activate();
}
private:
Browser* browser_;
bool is_active_ = false;
ui::WindowShowState show_state_ = ui::SHOW_STATE_NORMAL;
DISALLOW_COPY_AND_ASSIGN(FakeBrowserWindow);
};
} // namespace
// Tests UKM entries generated by WindowMetricsLogger at the request of
// WindowActivityWatcher.
class WindowActivityWatcherTest : public ChromeRenderViewHostTestHarness {
protected:
WindowActivityWatcherTest() = default;
~WindowActivityWatcherTest() override { EXPECT_FALSE(WasNewEntryRecorded()); }
void SetUp() override {
ChromeRenderViewHostTestHarness::SetUp();
// Start TabActivityWatcher so it logs TabMetrics UKMs.
resource_coordinator::TabActivityWatcher::GetInstance();
}
// Adds a tab and simulates a basic navigation.
void AddTab(Browser* browser) {
content::WebContentsTester::For(
tab_activity_simulator_.AddWebContentsAndNavigate(
browser->tab_strip_model(), GURL(kTestUrl)))
->TestSetIsLoading(false);
}
bool WasNewEntryRecorded() {
return ukm_entry_checker_.NumNewEntriesRecorded(kEntryName) > 0;
}
UkmEntryChecker* ukm_entry_checker() { return &ukm_entry_checker_; }
private:
UkmEntryChecker ukm_entry_checker_;
TabActivitySimulator tab_activity_simulator_;
DISALLOW_COPY_AND_ASSIGN(WindowActivityWatcherTest);
};
// Tests UKM logging of two browser windows.
TEST_F(WindowActivityWatcherTest, Basic) {
Browser::CreateParams params(profile(), true);
std::unique_ptr<Browser> browser =
FakeBrowserWindow::CreateBrowserWithFakeWindowForParams(&params);
AddTab(browser.get());
UkmMetricMap expected_metrics({
{TabManager_WindowMetrics::kWindowIdName, browser->session_id().id()},
{TabManager_WindowMetrics::kShowStateName,
WindowMetricsEvent::SHOW_STATE_NORMAL},
{TabManager_WindowMetrics::kTypeName, WindowMetricsEvent::TYPE_TABBED},
{TabManager_WindowMetrics::kIsActiveName, 1},
{TabManager_WindowMetrics::kTabCountName, 1},
});
{
SCOPED_TRACE("");
// Window UKMs are not expected to be associated with any particular URL.
ukm_entry_checker()->ExpectNewEntry(kEntryName, GURL(), expected_metrics);
}
AddTab(browser.get());
expected_metrics[TabManager_WindowMetrics::kTabCountName].value()++;
{
SCOPED_TRACE("");
// Window UKMs are not expected to be associated with any particular URL.
ukm_entry_checker()->ExpectNewEntry(kEntryName, GURL(), expected_metrics);
}
browser->window()->Minimize();
expected_metrics[TabManager_WindowMetrics::kShowStateName] =
WindowMetricsEvent::SHOW_STATE_MINIMIZED;
expected_metrics[TabManager_WindowMetrics::kIsActiveName] = 0;
{
SCOPED_TRACE("");
ukm_entry_checker()->ExpectNewEntry(kEntryName, GURL(), expected_metrics);
}
// A new entry is not created if nothing changes.
EXPECT_FALSE(WasNewEntryRecorded());
// A second browser can be logged.
Browser::CreateParams params_2(Browser::TYPE_POPUP, profile(), true);
std::unique_ptr<Browser> browser_2 =
FakeBrowserWindow::CreateBrowserWithFakeWindowForParams(&params_2);
// An entry is not logged until a tab is added to the new window.
EXPECT_FALSE(WasNewEntryRecorded());
AddTab(browser_2.get());
UkmMetricMap expected_metrics_2({
{TabManager_WindowMetrics::kWindowIdName, browser_2->session_id().id()},
{TabManager_WindowMetrics::kShowStateName,
WindowMetricsEvent::SHOW_STATE_NORMAL},
{TabManager_WindowMetrics::kTypeName, WindowMetricsEvent::TYPE_POPUP},
{TabManager_WindowMetrics::kIsActiveName, 1},
{TabManager_WindowMetrics::kTabCountName, 1},
});
{
SCOPED_TRACE("");
ukm_entry_checker()->ExpectNewEntry(kEntryName, GURL(), expected_metrics_2);
}
// Switching the active browser logs two events.
browser_2->window()->Deactivate();
expected_metrics_2[TabManager_WindowMetrics::kIsActiveName] = 0;
{
SCOPED_TRACE("");
ukm_entry_checker()->ExpectNewEntry(kEntryName, GURL(), expected_metrics_2);
}
browser->window()->Restore();
expected_metrics[TabManager_WindowMetrics::kShowStateName] =
WindowMetricsEvent::SHOW_STATE_NORMAL;
expected_metrics[TabManager_WindowMetrics::kIsActiveName] = 1;
{
SCOPED_TRACE("");
ukm_entry_checker()->ExpectNewEntry(kEntryName, GURL(), expected_metrics);
}
browser->tab_strip_model()->CloseAllTabs();
browser_2->tab_strip_model()->CloseAllTabs();
}
// Tests moving a tab between browser windows.
TEST_F(WindowActivityWatcherTest, MoveTabToOtherWindow) {
Browser::CreateParams params(profile(), true);
std::unique_ptr<Browser> starting_browser =
FakeBrowserWindow::CreateBrowserWithFakeWindowForParams(&params);
AddTab(starting_browser.get());
UkmMetricMap starting_browser_metrics({
{TabManager_WindowMetrics::kWindowIdName,
starting_browser->session_id().id()},
{TabManager_WindowMetrics::kIsActiveName, 1},
{TabManager_WindowMetrics::kTabCountName, 1},
});
{
SCOPED_TRACE("");
ukm_entry_checker()->ExpectNewEntry(kEntryName, GURL(),
starting_browser_metrics);
}
// Add a second tab, so we can detach it while leaving the original window
// behind.
AddTab(starting_browser.get());
starting_browser_metrics[TabManager_WindowMetrics::kTabCountName].value() = 2;
{
SCOPED_TRACE("");
ukm_entry_checker()->ExpectNewEntry(kEntryName, GURL(),
starting_browser_metrics);
}
// Drag the tab out of its window.
std::unique_ptr<content::WebContents> dragged_tab =
starting_browser->tab_strip_model()->DetachWebContentsAt(1);
starting_browser_metrics[TabManager_WindowMetrics::kTabCountName].value() = 1;
{
SCOPED_TRACE("");
ukm_entry_checker()->ExpectNewEntry(kEntryName, GURL(),
starting_browser_metrics);
}
starting_browser->window()->Deactivate();
starting_browser_metrics[TabManager_WindowMetrics::kIsActiveName].value() =
false;
{
SCOPED_TRACE("");
ukm_entry_checker()->ExpectNewEntry(kEntryName, GURL(),
starting_browser_metrics);
}
// Create a new Browser for the tab.
std::unique_ptr<Browser> created_browser =
FakeBrowserWindow::CreateBrowserWithFakeWindowForParams(&params);
created_browser->window()->Activate();
created_browser->tab_strip_model()->InsertWebContentsAt(
0, std::move(dragged_tab), TabStripModel::ADD_ACTIVE);
UkmMetricMap created_browser_metrics({
{TabManager_WindowMetrics::kWindowIdName,
created_browser->session_id().id()},
{TabManager_WindowMetrics::kIsActiveName, 1},
{TabManager_WindowMetrics::kTabCountName, 1},
});
{
SCOPED_TRACE("");
ukm_entry_checker()->ExpectNewEntry(kEntryName, GURL(),
created_browser_metrics);
}
starting_browser->tab_strip_model()->CloseAllTabs();
created_browser->tab_strip_model()->CloseAllTabs();
}
// Tests that a replaced tab still causes event logging upon being detached.
TEST_F(WindowActivityWatcherTest, ReplaceTab) {
Browser::CreateParams params(profile(), true);
std::unique_ptr<Browser> browser =
FakeBrowserWindow::CreateBrowserWithFakeWindowForParams(&params);
AddTab(browser.get());
UkmMetricMap expected_metrics({
{TabManager_WindowMetrics::kWindowIdName, browser->session_id().id()},
{TabManager_WindowMetrics::kTabCountName, 1},
});
{
SCOPED_TRACE("");
ukm_entry_checker()->ExpectNewEntry(kEntryName, GURL(), expected_metrics);
}
// Add a tab that will be replaced.
AddTab(browser.get());
expected_metrics[TabManager_WindowMetrics::kTabCountName].value() = 2;
{
SCOPED_TRACE("");
ukm_entry_checker()->ExpectNewEntry(kEntryName, GURL(), expected_metrics);
}
// Replace the tab.
content::WebContents::CreateParams web_contents_params(profile(), nullptr);
std::unique_ptr<content::WebContents> new_contents = base::WrapUnique(
content::WebContentsTester::CreateTestWebContents(web_contents_params));
std::unique_ptr<content::WebContents> old_contents =
browser->tab_strip_model()->ReplaceWebContentsAt(1,
std::move(new_contents));
// Close the replaced tab. This should log an event with an updated TabCount.
browser->tab_strip_model()->CloseWebContentsAt(1, TabStripModel::CLOSE_NONE);
expected_metrics[TabManager_WindowMetrics::kTabCountName].value() = 1;
{
SCOPED_TRACE("");
ukm_entry_checker()->ExpectNewEntry(kEntryName, GURL(), expected_metrics);
}
browser->tab_strip_model()->CloseAllTabs();
}
// Tests that counts are properly bucketized.
TEST_F(WindowActivityWatcherTest, Counts) {
Browser::CreateParams params(profile(), true);
std::unique_ptr<Browser> browser =
FakeBrowserWindow::CreateBrowserWithFakeWindowForParams(&params);
UkmMetricMap expected_metrics;
// Expected buckets for each tab count using a spacing factor of 1.5.
std::vector<int> expected_buckets = {
0, // Not actually tested, since we don't log windows with 0 tabs.
1, 2, 3, // Values up to 3 should be represented exactly.
4, 4, 6, 6, 8, 8, 8, 8, 12, 12, 12, 12, 12, 12, 18,
};
for (size_t i = 1; i < expected_buckets.size(); i++) {
SCOPED_TRACE(base::StringPrintf("Tab count: %zd", i));
AddTab(browser.get());
expected_metrics[TabManager_WindowMetrics::kTabCountName] =
expected_buckets[i];
ukm_entry_checker()->ExpectNewEntry(kEntryName, GURL(), expected_metrics);
}
browser->tab_strip_model()->CloseAllTabs();
}
// Tests that incognito windows are ignored.
TEST_F(WindowActivityWatcherTest, Incognito) {
Browser::CreateParams params(profile()->GetOffTheRecordProfile(), true);
std::unique_ptr<Browser> browser =
FakeBrowserWindow::CreateBrowserWithFakeWindowForParams(&params);
AddTab(browser.get());
EXPECT_EQ(0u, ukm_entry_checker()->NumEntries(kEntryName));
browser->tab_strip_model()->CloseAllTabs();
}