blob: 7f6cfb7abbf373a89c477500afc7a95bdcecd776 [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/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(&params);
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(&params);
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(&params);
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(&params);
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(&params);
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(&params);
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