| // 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(¶ms); |
| 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(¶ms_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(¶ms); |
| 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(¶ms); |
| 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(¶ms); |
| 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(¶ms); |
| 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(¶ms); |
| AddTab(browser.get()); |
| EXPECT_EQ(0u, ukm_entry_checker()->NumEntries(kEntryName)); |
| |
| browser->tab_strip_model()->CloseAllTabs(); |
| } |