| // 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/resource_coordinator/tab_activity_watcher.h" |
| |
| #include <memory> |
| |
| #include "base/macros.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/engagement/site_engagement_service.h" |
| #include "chrome/browser/resource_coordinator/tab_activity_watcher.h" |
| #include "chrome/browser/resource_coordinator/tab_manager.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/tabs/tab_metrics_event.pb.h" |
| #include "chrome/browser/ui/tabs/tab_metrics_logger_impl.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/browser/ui/tabs/tab_ukm_test_helper.h" |
| #include "chrome/test/base/test_browser_window.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "components/ukm/ukm_source.h" |
| #include "content/public/browser/render_view_host.h" |
| #include "content/public/browser/render_widget_host.h" |
| #include "content/public/test/web_contents_tester.h" |
| #include "services/metrics/public/cpp/ukm_builders.h" |
| #include "services/metrics/public/interfaces/ukm_interface.mojom.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/WebKit/public/platform/WebInputEvent.h" |
| #include "third_party/WebKit/public/platform/WebMouseEvent.h" |
| |
| using blink::WebInputEvent; |
| using content::WebContentsTester; |
| using metrics::TabMetricsEvent; |
| using ukm::builders::TabManager_TabMetrics; |
| |
| namespace resource_coordinator { |
| namespace { |
| |
| const char* kEntryName = TabManager_TabMetrics::kEntryName; |
| |
| const GURL kTestUrls[] = { |
| GURL("https://example.com/"), GURL("https://google.fake"), |
| GURL("https://example3.com"), |
| }; |
| |
| // The default metric values for a tab. |
| const UkmMetricMap kBasicMetricValues({ |
| {TabManager_TabMetrics::kContentTypeName, |
| TabMetricsEvent::CONTENT_TYPE_TEXT_HTML}, |
| // TODO(michaelpg): Test HasBeforeUnloadHandler in a browser_test. |
| {TabManager_TabMetrics::kHasBeforeUnloadHandlerName, 0}, |
| {TabManager_TabMetrics::kHasFormEntryName, 0}, |
| {TabManager_TabMetrics::kIsExtensionProtectedName, 0}, |
| {TabManager_TabMetrics::kIsPinnedName, 0}, |
| {TabManager_TabMetrics::kKeyEventCountName, 0}, |
| {TabManager_TabMetrics::kMouseEventCountName, 0}, |
| {TabManager_TabMetrics::kSiteEngagementScoreName, 0}, |
| {TabManager_TabMetrics::kTouchEventCountName, 0}, |
| {TabManager_TabMetrics::kWasRecentlyAudibleName, 0}, |
| {TabManager_TabMetrics::kDefaultProtocolHandlerName, base::nullopt}, |
| }); |
| |
| blink::WebMouseEvent CreateMouseEvent(WebInputEvent::Type event_type) { |
| return blink::WebMouseEvent(event_type, WebInputEvent::kNoModifiers, |
| WebInputEvent::kTimeStampForTesting); |
| } |
| |
| } // namespace |
| |
| // Tests UKM entries generated by TabMetricsLogger at the request of |
| // TabActivityWatcher. |
| class TabActivityWatcherTest : public TabActivityTestBase { |
| protected: |
| TabActivityWatcherTest() { |
| TabActivityWatcher::GetInstance()->DisableLogTimeoutForTesting(); |
| TabActivityWatcher::GetInstance()->ResetForTesting(); |
| } |
| |
| void TearDown() override { |
| EXPECT_EQ(0, ukm_entry_checker_.NumNewEntriesRecorded(kEntryName)); |
| TabActivityWatcher::GetInstance()->ResetForTesting(); |
| TabActivityTestBase::TearDown(); |
| } |
| |
| void ExpectNewEntry(const GURL& source_url, |
| const UkmMetricMap& expected_metrics) { |
| ukm_entry_checker_.ExpectNewEntry(kEntryName, source_url, expected_metrics); |
| |
| const size_t num_entries = ukm_entry_checker_.NumEntries(kEntryName); |
| const ukm::mojom::UkmEntry* last_entry = |
| ukm_entry_checker_.LastUkmEntry(kEntryName); |
| ukm::TestUkmRecorder::ExpectEntryMetric( |
| last_entry, TabManager_TabMetrics::kSequenceIdName, num_entries); |
| } |
| |
| protected: |
| UkmEntryChecker ukm_entry_checker_; |
| |
| private: |
| TabMetricsLoggerImpl tab_metrics_logger_; |
| DISALLOW_COPY_AND_ASSIGN(TabActivityWatcherTest); |
| }; |
| |
| TEST_F(TabActivityWatcherTest, Basic) { |
| Browser::CreateParams params(profile(), true); |
| std::unique_ptr<Browser> browser = |
| CreateBrowserWithTestWindowForParams(¶ms); |
| |
| TabStripModel* tab_strip_model = browser->tab_strip_model(); |
| content::WebContents* fg_contents = |
| AddWebContentsAndNavigate(tab_strip_model, GURL(kTestUrls[0])); |
| tab_strip_model->ActivateTabAt(0, false); |
| WebContentsTester::For(fg_contents)->TestSetIsLoading(false); |
| |
| // Adding, loading and activating a foreground tab doesn't trigger logging. |
| EXPECT_EQ(0, ukm_entry_checker_.NumNewEntriesRecorded(kEntryName)); |
| |
| // The second web contents is added as a background tab, so it logs an entry |
| // when it stops loading. |
| content::WebContents* bg_contents = |
| AddWebContentsAndNavigate(tab_strip_model, GURL(kTestUrls[1])); |
| WebContentsTester::For(bg_contents)->TestSetIsLoading(false); |
| ExpectNewEntry(kTestUrls[1], kBasicMetricValues); |
| |
| // Activating a tab logs the deactivated tab. |
| SwitchToTabAt(tab_strip_model, 1); |
| { |
| SCOPED_TRACE(""); |
| ExpectNewEntry(kTestUrls[0], kBasicMetricValues); |
| } |
| |
| SwitchToTabAt(tab_strip_model, 0); |
| { |
| SCOPED_TRACE(""); |
| ExpectNewEntry(kTestUrls[1], kBasicMetricValues); |
| } |
| |
| // Closing the tabs destroys the WebContentses but should not trigger logging. |
| // The TestWebContentsObserver simulates hiding these tabs as they are closed; |
| // we verify in TearDown() that no logging occurred. |
| tab_strip_model->CloseAllTabs(); |
| } |
| |
| // Tests when tab events like pinning and navigating trigger logging. |
| TEST_F(TabActivityWatcherTest, TabEvents) { |
| Browser::CreateParams params(profile(), true); |
| std::unique_ptr<Browser> browser = |
| CreateBrowserWithTestWindowForParams(¶ms); |
| |
| TabStripModel* tab_strip_model = browser->tab_strip_model(); |
| content::WebContents* test_contents_1 = |
| AddWebContentsAndNavigate(tab_strip_model, GURL(kTestUrls[0])); |
| tab_strip_model->ActivateTabAt(0, false); |
| |
| // Opening the background tab triggers logging once the page finishes loading. |
| content::WebContents* test_contents_2 = |
| AddWebContentsAndNavigate(tab_strip_model, GURL(kTestUrls[1])); |
| EXPECT_EQ(0, ukm_entry_checker_.NumNewEntriesRecorded(kEntryName)); |
| WebContentsTester::For(test_contents_2)->TestSetIsLoading(false); |
| { |
| SCOPED_TRACE(""); |
| ExpectNewEntry(GURL(kTestUrls[1]), kBasicMetricValues); |
| } |
| |
| // Navigating the active tab doesn't trigger logging. |
| WebContentsTester::For(test_contents_1)->NavigateAndCommit(kTestUrls[2]); |
| WebContentsTester::For(test_contents_1)->TestSetIsLoading(false); |
| EXPECT_EQ(0, ukm_entry_checker_.NumNewEntriesRecorded(kEntryName)); |
| |
| // Pinning the active tab doesn't trigger logging. |
| tab_strip_model->SetTabPinned(0, true); |
| EXPECT_EQ(0, ukm_entry_checker_.NumNewEntriesRecorded(kEntryName)); |
| |
| // Pinning and unpinning the background tab triggers logging. |
| tab_strip_model->SetTabPinned(1, true); |
| UkmMetricMap expected_metrics(kBasicMetricValues); |
| expected_metrics[TabManager_TabMetrics::kIsPinnedName] = 1; |
| { |
| SCOPED_TRACE(""); |
| ExpectNewEntry(GURL(kTestUrls[1]), expected_metrics); |
| } |
| tab_strip_model->SetTabPinned(1, false); |
| expected_metrics[TabManager_TabMetrics::kIsPinnedName] = 0; |
| { |
| SCOPED_TRACE(""); |
| ExpectNewEntry(GURL(kTestUrls[1]), kBasicMetricValues); |
| } |
| |
| // Navigating the background tab triggers logging once the page finishes |
| // loading. |
| WebContentsTester::For(test_contents_2)->NavigateAndCommit(kTestUrls[0]); |
| EXPECT_EQ(0, ukm_entry_checker_.NumNewEntriesRecorded(kEntryName)); |
| WebContentsTester::For(test_contents_2)->TestSetIsLoading(false); |
| { |
| SCOPED_TRACE(""); |
| ExpectNewEntry(GURL(kTestUrls[0]), kBasicMetricValues); |
| } |
| |
| tab_strip_model->CloseAllTabs(); |
| } |
| |
| // Tests setting and changing tab metrics. |
| TEST_F(TabActivityWatcherTest, TabMetrics) { |
| Browser::CreateParams params(profile(), true); |
| std::unique_ptr<Browser> browser = |
| CreateBrowserWithTestWindowForParams(¶ms); |
| |
| TabStripModel* tab_strip_model = browser->tab_strip_model(); |
| content::WebContents* test_contents_1 = |
| AddWebContentsAndNavigate(tab_strip_model, GURL(kTestUrls[0])); |
| tab_strip_model->ActivateTabAt(0, false); |
| |
| // Expected metrics for tab event. |
| UkmMetricMap expected_metrics(kBasicMetricValues); |
| |
| // Load background contents and verify UKM entry. |
| content::WebContents* test_contents_2 = |
| AddWebContentsAndNavigate(tab_strip_model, GURL(kTestUrls[1])); |
| WebContentsTester::For(test_contents_2)->TestSetIsLoading(false); |
| { |
| SCOPED_TRACE(""); |
| ExpectNewEntry(kTestUrls[1], expected_metrics); |
| } |
| |
| // Simulate an extension protecting the tab. |
| g_browser_process->GetTabManager()->SetTabAutoDiscardableState( |
| test_contents_2, false); |
| expected_metrics[TabManager_TabMetrics::kIsExtensionProtectedName] = 1; |
| |
| // Site engagement score should round down to the nearest 10. |
| SiteEngagementService::Get(profile())->ResetBaseScoreForURL(kTestUrls[1], 45); |
| expected_metrics[TabManager_TabMetrics::kSiteEngagementScoreName] = 40; |
| |
| WebContentsTester::For(test_contents_2)->SetWasRecentlyAudible(true); |
| expected_metrics[TabManager_TabMetrics::kWasRecentlyAudibleName] = 1; |
| |
| // Pin the background tab to log an event. (This moves it to index 0.) |
| tab_strip_model->SetTabPinned(1, true); |
| expected_metrics[TabManager_TabMetrics::kIsPinnedName] = 1; |
| { |
| SCOPED_TRACE(""); |
| ExpectNewEntry(kTestUrls[1], expected_metrics); |
| } |
| |
| // Unset WasRecentlyAudible and navigate the background tab to a new domain. |
| // Site engagement score for the new domain is 0. |
| WebContentsTester::For(test_contents_2)->SetWasRecentlyAudible(false); |
| expected_metrics[TabManager_TabMetrics::kWasRecentlyAudibleName] = 0; |
| WebContentsTester::For(test_contents_2)->NavigateAndCommit(kTestUrls[2]); |
| expected_metrics[TabManager_TabMetrics::kSiteEngagementScoreName] = 0; |
| |
| WebContentsTester::For(test_contents_2)->TestSetIsLoading(false); |
| { |
| SCOPED_TRACE(""); |
| ExpectNewEntry(kTestUrls[2], expected_metrics); |
| } |
| |
| // Navigate the active tab and switch away from it. The entry should reflect |
| // the new URL (even when the page hasn't finished loading). |
| WebContentsTester::For(test_contents_1)->NavigateAndCommit(kTestUrls[2]); |
| SwitchToTabAt(tab_strip_model, 0); |
| { |
| SCOPED_TRACE(""); |
| // This tab still has the default metrics. |
| ExpectNewEntry(kTestUrls[2], kBasicMetricValues); |
| } |
| |
| tab_strip_model->CloseAllTabs(); |
| } |
| |
| // Tests counting input events. TODO(michaelpg): Currently only tests mouse |
| // events. |
| TEST_F(TabActivityWatcherTest, InputEvents) { |
| Browser::CreateParams params(profile(), true); |
| std::unique_ptr<Browser> browser = |
| CreateBrowserWithTestWindowForParams(¶ms); |
| |
| TabStripModel* tab_strip_model = browser->tab_strip_model(); |
| content::WebContents* test_contents_1 = |
| AddWebContentsAndNavigate(tab_strip_model, GURL(kTestUrls[0])); |
| content::WebContents* test_contents_2 = |
| AddWebContentsAndNavigate(tab_strip_model, GURL(kTestUrls[1])); |
| tab_strip_model->ActivateTabAt(0, false); |
| |
| UkmMetricMap expected_metrics_1(kBasicMetricValues); |
| UkmMetricMap expected_metrics_2(kBasicMetricValues); |
| |
| // Fake some input events. |
| content::RenderWidgetHost* widget_1 = |
| test_contents_1->GetRenderViewHost()->GetWidget(); |
| widget_1->ForwardMouseEvent(CreateMouseEvent(WebInputEvent::kMouseDown)); |
| widget_1->ForwardMouseEvent(CreateMouseEvent(WebInputEvent::kMouseUp)); |
| widget_1->ForwardMouseEvent(CreateMouseEvent(WebInputEvent::kMouseMove)); |
| expected_metrics_1[TabManager_TabMetrics::kMouseEventCountName] = 3; |
| |
| // Switch to the background tab. The current tab is deactivated and logged. |
| SwitchToTabAt(tab_strip_model, 1); |
| { |
| SCOPED_TRACE(""); |
| ExpectNewEntry(kTestUrls[0], expected_metrics_1); |
| } |
| |
| // The second tab's counts are independent of the other's. |
| content::RenderWidgetHost* widget_2 = |
| test_contents_2->GetRenderViewHost()->GetWidget(); |
| widget_2->ForwardMouseEvent(CreateMouseEvent(WebInputEvent::kMouseMove)); |
| expected_metrics_2[TabManager_TabMetrics::kMouseEventCountName] = 1; |
| |
| // Switch back to the first tab to log the second tab. |
| SwitchToTabAt(tab_strip_model, 0); |
| { |
| SCOPED_TRACE(""); |
| ExpectNewEntry(kTestUrls[1], expected_metrics_2); |
| } |
| |
| // New events are added to the first tab's existing counts. |
| widget_1->ForwardMouseEvent(CreateMouseEvent(WebInputEvent::kMouseMove)); |
| widget_1->ForwardMouseEvent(CreateMouseEvent(WebInputEvent::kMouseMove)); |
| expected_metrics_1[TabManager_TabMetrics::kMouseEventCountName] = 5; |
| test_contents_1->WasHidden(); |
| { |
| SCOPED_TRACE(""); |
| ExpectNewEntry(kTestUrls[0], expected_metrics_1); |
| } |
| test_contents_1->WasShown(); |
| |
| // After a navigation, test that the counts are reset. |
| WebContentsTester::For(test_contents_1)->NavigateAndCommit(kTestUrls[2]); |
| // The widget may have been invalidated by the navigation. |
| widget_1 = test_contents_1->GetRenderViewHost()->GetWidget(); |
| widget_1->ForwardMouseEvent(CreateMouseEvent(WebInputEvent::kMouseMove)); |
| expected_metrics_1[TabManager_TabMetrics::kMouseEventCountName] = 1; |
| test_contents_1->WasHidden(); |
| { |
| SCOPED_TRACE(""); |
| ExpectNewEntry(kTestUrls[2], expected_metrics_1); |
| } |
| |
| tab_strip_model->CloseAllTabs(); |
| } |
| |
| // Tests that logging happens when the browser window is hidden (even if the |
| // WebContents is still the active tab). |
| TEST_F(TabActivityWatcherTest, HideWindow) { |
| Browser::CreateParams params(profile(), true); |
| std::unique_ptr<Browser> browser = |
| CreateBrowserWithTestWindowForParams(¶ms); |
| |
| TabStripModel* tab_strip_model = browser->tab_strip_model(); |
| content::WebContents* test_contents = |
| AddWebContentsAndNavigate(tab_strip_model, GURL(kTestUrls[0])); |
| tab_strip_model->ActivateTabAt(0, false); |
| |
| // Hiding the window triggers the log. |
| test_contents->WasHidden(); |
| { |
| SCOPED_TRACE(""); |
| ExpectNewEntry(kTestUrls[0], kBasicMetricValues); |
| } |
| |
| // Showing the window does not. |
| test_contents->WasShown(); |
| EXPECT_EQ(0, ukm_entry_checker_.NumNewEntriesRecorded(kEntryName)); |
| |
| tab_strip_model->CloseAllTabs(); |
| } |
| |
| // Tests navigation-related metrics. |
| TEST_F(TabActivityWatcherTest, Navigations) { |
| Browser::CreateParams params(profile(), true); |
| auto browser = CreateBrowserWithTestWindowForParams(¶ms); |
| TabStripModel* tab_strip_model = browser->tab_strip_model(); |
| |
| // Set up first tab. |
| AddWebContentsAndNavigate(tab_strip_model, GURL(kTestUrls[0])); |
| tab_strip_model->ActivateTabAt(0, false); |
| |
| // Expected metrics for tab event. |
| UkmMetricMap expected_metrics(kBasicMetricValues); |
| |
| // Load background contents and verify UKM entry. |
| content::WebContents* test_contents = AddWebContentsAndNavigate( |
| tab_strip_model, GURL(kTestUrls[1]), |
| ui::PageTransitionFromInt(ui::PAGE_TRANSITION_TYPED | |
| ui::PAGE_TRANSITION_FROM_ADDRESS_BAR)); |
| WebContentsTester::For(test_contents)->TestSetIsLoading(false); |
| expected_metrics[TabManager_TabMetrics::kPageTransitionCoreTypeName] = |
| base::nullopt; |
| expected_metrics[TabManager_TabMetrics::kPageTransitionFromAddressBarName] = |
| true; |
| expected_metrics[TabManager_TabMetrics::kPageTransitionIsRedirectName] = |
| false; |
| expected_metrics[TabManager_TabMetrics::kNavigationEntryCountName] = 1; |
| { |
| SCOPED_TRACE(""); |
| ExpectNewEntry(kTestUrls[1], expected_metrics); |
| } |
| |
| // Navigate background tab (not all transition types make sense in the |
| // background, but this is simpler than juggling two tabs to trigger logging). |
| Navigate(test_contents, kTestUrls[2], ui::PAGE_TRANSITION_LINK); |
| WebContentsTester::For(test_contents)->TestSetIsLoading(false); |
| expected_metrics[TabManager_TabMetrics::kPageTransitionCoreTypeName] = |
| ui::PAGE_TRANSITION_LINK; |
| expected_metrics[TabManager_TabMetrics::kPageTransitionFromAddressBarName] = |
| false; |
| expected_metrics[TabManager_TabMetrics::kPageTransitionIsRedirectName] = |
| false; |
| expected_metrics[TabManager_TabMetrics::kNavigationEntryCountName].value()++; |
| { |
| SCOPED_TRACE(""); |
| ExpectNewEntry(kTestUrls[2], expected_metrics); |
| } |
| |
| Navigate(test_contents, kTestUrls[0], |
| ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK | |
| ui::PAGE_TRANSITION_SERVER_REDIRECT)); |
| WebContentsTester::For(test_contents)->TestSetIsLoading(false); |
| expected_metrics[TabManager_TabMetrics::kPageTransitionCoreTypeName] = |
| ui::PAGE_TRANSITION_LINK; |
| expected_metrics[TabManager_TabMetrics::kPageTransitionFromAddressBarName] = |
| false; |
| expected_metrics[TabManager_TabMetrics::kPageTransitionIsRedirectName] = true; |
| expected_metrics[TabManager_TabMetrics::kNavigationEntryCountName].value()++; |
| { |
| SCOPED_TRACE(""); |
| ExpectNewEntry(kTestUrls[0], expected_metrics); |
| } |
| |
| Navigate(test_contents, kTestUrls[0], ui::PAGE_TRANSITION_RELOAD); |
| WebContentsTester::For(test_contents)->TestSetIsLoading(false); |
| expected_metrics[TabManager_TabMetrics::kPageTransitionCoreTypeName] = |
| ui::PAGE_TRANSITION_RELOAD; |
| expected_metrics[TabManager_TabMetrics::kNavigationEntryCountName].value()++; |
| expected_metrics[TabManager_TabMetrics::kPageTransitionFromAddressBarName] = |
| false; |
| expected_metrics[TabManager_TabMetrics::kPageTransitionIsRedirectName] = |
| false; |
| { |
| SCOPED_TRACE(""); |
| ExpectNewEntry(kTestUrls[0], expected_metrics); |
| } |
| |
| Navigate(test_contents, kTestUrls[1], ui::PAGE_TRANSITION_AUTO_BOOKMARK); |
| WebContentsTester::For(test_contents)->TestSetIsLoading(false); |
| expected_metrics[TabManager_TabMetrics::kPageTransitionCoreTypeName] = |
| ui::PAGE_TRANSITION_AUTO_BOOKMARK; |
| // FromAddressBar and IsRedirect should still be false, no need to update |
| // their values in |expected_metrics|. |
| expected_metrics[TabManager_TabMetrics::kNavigationEntryCountName].value()++; |
| { |
| SCOPED_TRACE(""); |
| ExpectNewEntry(kTestUrls[1], expected_metrics); |
| } |
| |
| Navigate(test_contents, kTestUrls[1], ui::PAGE_TRANSITION_FORM_SUBMIT); |
| WebContentsTester::For(test_contents)->TestSetIsLoading(false); |
| expected_metrics[TabManager_TabMetrics::kPageTransitionCoreTypeName] = |
| ui::PAGE_TRANSITION_FORM_SUBMIT; |
| expected_metrics[TabManager_TabMetrics::kNavigationEntryCountName].value()++; |
| { |
| SCOPED_TRACE(""); |
| ExpectNewEntry(kTestUrls[1], expected_metrics); |
| } |
| |
| // Test non-reportable core type. |
| Navigate(test_contents, kTestUrls[0], |
| ui::PageTransitionFromInt(ui::PAGE_TRANSITION_KEYWORD | |
| ui::PAGE_TRANSITION_FROM_ADDRESS_BAR)); |
| WebContentsTester::For(test_contents)->TestSetIsLoading(false); |
| expected_metrics[TabManager_TabMetrics::kPageTransitionCoreTypeName] = |
| base::nullopt; |
| expected_metrics[TabManager_TabMetrics::kPageTransitionFromAddressBarName] = |
| true; |
| expected_metrics[TabManager_TabMetrics::kNavigationEntryCountName].value()++; |
| { |
| SCOPED_TRACE(""); |
| ExpectNewEntry(kTestUrls[0], expected_metrics); |
| } |
| |
| tab_strip_model->CloseAllTabs(); |
| } |
| |
| } // namespace resource_coordinator |