Commit d2347bcf authored by Yashar Dabiran's avatar Yashar Dabiran Committed by Chromium LUCI CQ

[CSN] Record number of back navigations to a SRP

This CL adds a new metric that records the number back navigations to
a Google search result page in a search session. This is part of efforts
to record metrics for CSN stage 1.

See go/csn-design for more details on the project.

Change-Id: Ida5ac7358764a8c13da5aea8890957f77c6e2a47
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2616325Reviewed-by: default avatarCaitlin Fischer <caitlinfischer@google.com>
Reviewed-by: default avatarYaron Friedman <yfriedman@chromium.org>
Reviewed-by: default avatarCalder Kitagawa <ckitagawa@chromium.org>
Reviewed-by: default avatarFred Mello <fredmello@chromium.org>
Reviewed-by: default avatarMehran Mahmoudi <mahmoudi@chromium.org>
Commit-Queue: Yashar Dabiran <yashard@chromium.org>
Cr-Commit-Position: refs/heads/master@{#845675}
parent 666e5520
......@@ -813,6 +813,7 @@ junit_binary("chrome_junit_tests") {
"//chrome/browser/browser_controls/android:java",
"//chrome/browser/browser_controls/android:junit",
"//chrome/browser/contextmenu:java",
"//chrome/browser/continuous_search/internal:junit",
"//chrome/browser/device:java",
"//chrome/browser/device:junit",
"//chrome/browser/download/android:java",
......
include_rules = [
"+content/public/android/java/src/org/chromium/content_public/browser/LoadUrlParams.java",
"+content/public/android/java/src/org/chromium/content_public/browser",
]
......@@ -19,10 +19,11 @@ public class ContinuousSearchTabHelper {
* @param tab to enable continuous search support for.
*/
public static void createForTab(Tab tab) {
if (!FeatureList.isNativeInitialized()
|| !ChromeFeatureList.isEnabled(ChromeFeatureList.CONTINUOUS_SEARCH)) {
return;
}
if (!FeatureList.isNativeInitialized()) return;
if (!tab.isIncognito()) new BackNavigationTabObserver(tab);
if (!ChromeFeatureList.isEnabled(ChromeFeatureList.CONTINUOUS_SEARCH)) return;
SearchResultUserData.createForTab(tab);
SearchResultListCoordinator.createForTab(tab);
......
......@@ -12,6 +12,7 @@ android_library("java") {
annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
sources = [
"android/java/src/org/chromium/chrome/browser/continuous_search/BackNavigationTabObserver.java",
"android/java/src/org/chromium/chrome/browser/continuous_search/ContinuousSearchTabObserver.java",
"android/java/src/org/chromium/chrome/browser/continuous_search/SearchResult.java",
"android/java/src/org/chromium/chrome/browser/continuous_search/SearchResultGroup.java",
......@@ -44,6 +45,27 @@ android_library("java") {
resources_package = "org.chromium.chrome.browser.continuous_search"
}
android_library("junit") {
bypass_platform_checks = true
testonly = true
sources = [ "android/junit/org/chromium/chrome/browser/continuous_search/BackNavigationTabObserverTest.java" ]
deps = [
":java",
"//base:base_java",
"//base:base_java_test_support",
"//base:base_junit_test_support",
"//chrome/browser/tab:java",
"//content/public/android:content_java",
"//third_party/android_deps:robolectric_all_java",
"//third_party/junit",
"//third_party/mockito:mockito_java",
"//url:gurl_java",
"//url:gurl_junit_test_support",
]
}
android_resources("java_resources") {
sources = [
"android/java/res/layout/continuous_search_list_ad.xml",
......
// Copyright 2021 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.
package org.chromium.chrome.browser.continuous_search;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.chrome.browser.tab.EmptyTabObserver;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabHidingType;
import org.chromium.content_public.browser.NavigationEntry;
import org.chromium.content_public.browser.NavigationHistory;
import org.chromium.url.GURL;
/**
* A tab observer to record the number of back navigations to SRP
*/
public class BackNavigationTabObserver extends EmptyTabObserver {
private GURL mLastSrpUrl;
private GURL mLastVisitedUrl;
private int mBackNavigationCount;
public BackNavigationTabObserver(Tab tab) {
tab.addObserver(this);
mLastSrpUrl = null;
mLastVisitedUrl = null;
mBackNavigationCount = 0;
}
@Override
public void onPageLoadFinished(Tab tab, GURL url) {
// Ignore page reloads
if (url.equals(mLastVisitedUrl)) return;
if (tab.getWebContents() == null) return;
NavigationHistory history =
tab.getWebContents().getNavigationController().getNavigationHistory();
// Some tests don't mock Tab completely and leave NavigationHistory with no entries.
// This check here is to prevent crashing in that scenario.
if (history.getEntryCount() == 0) return;
NavigationEntry entry = history.getEntryAtIndex(history.getCurrentEntryIndex());
if (SearchUrlHelper.isSrpUrl(entry.getUrl())) {
if (entry.getUrl().equals(mLastSrpUrl)) {
// Treat re-navigation to the last seen SRP as a back navigation.
mBackNavigationCount++;
} else {
// Encountered a new SRP session. Record the previous session and start a new one.
recordMetricAndClearCache();
mLastSrpUrl = entry.getUrl();
}
// A page opened through SRP has google.com as its referrer URL. Treat other cases as
// navigating away from the SRP session.
} else if (!SearchUrlHelper.isGoogleDomainUrl(entry.getReferrerUrl())) {
recordMetricAndClearCache();
}
mLastVisitedUrl = entry.getUrl();
}
@Override
public void onContentChanged(Tab tab) {
if (tab.isNativePage()) {
recordMetricAndClearCache();
}
}
@Override
public void onHidden(Tab tab, int reason) {
if (reason == TabHidingType.ACTIVITY_HIDDEN) {
recordMetricAndClearCache();
}
}
@Override
public void onDestroyed(Tab tab) {
recordMetricAndClearCache();
tab.removeObserver(this);
}
private void recordMetricAndClearCache() {
if (mLastSrpUrl != null) {
RecordHistogram.recordCount100Histogram(
"Browser.ContinuousSearch.BackNavigationToSrp", mBackNavigationCount);
}
mLastSrpUrl = null;
mBackNavigationCount = 0;
}
}
......@@ -15,6 +15,26 @@ import org.chromium.url.GURL;
public class SearchUrlHelper {
private SearchUrlHelper() {}
/**
* Checks whether the provided url is valid, the host is "www.google.<TLD>" with a valid TLD and
* has an HTTP or HTTPS scheme. Returns false if the url doesn't use the standard port for its
* scheme (80 for HTTP, 443 for HTTPS).
* @param url the url to check the criteria against.
* @return true if url satisfies all the requirements above.
*/
public static boolean isGoogleDomainUrl(GURL url) {
return SearchUrlHelperJni.get().isGoogleDomainUrl(url);
}
/**
* Checks whether the provided url represents a valid Google search url.
* @param url the url to check.
* @return true if the url satisfies the criteria.
*/
public static boolean isSrpUrl(GURL url) {
return SearchUrlHelperJni.get().isSrpUrl(url);
}
/**
* Gets the query of the provided url if it is a SRP URL.
* @param url The url to try to extract the query from.
......@@ -26,6 +46,8 @@ public class SearchUrlHelper {
@NativeMethods
interface Natives {
boolean isGoogleDomainUrl(GURL url);
boolean isSrpUrl(GURL url);
String getQueryIfSrpUrl(GURL url);
}
}
// Copyright 2021 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.
package org.chromium.chrome.browser.continuous_search;
import static junit.framework.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
import org.chromium.base.metrics.test.ShadowRecordHistogram;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabHidingType;
import org.chromium.content_public.browser.NavigationController;
import org.chromium.content_public.browser.NavigationEntry;
import org.chromium.content_public.browser.NavigationHistory;
import org.chromium.content_public.browser.WebContents;
import org.chromium.url.GURL;
import org.chromium.url.JUnitTestGURLs;
/**
* Unit tests for the {@link BackNavigationTabObserver} class.
*/
@RunWith(BaseRobolectricTestRunner.class)
@Config(shadows = {ShadowRecordHistogram.class})
public class BackNavigationTabObserverTest {
private static final String HISTOGRAM = "Browser.ContinuousSearch.BackNavigationToSrp";
@Rule
public JniMocker mMocker = new JniMocker();
@Mock
private Tab mTab;
@Mock
private NavigationController mNavigationController;
@Mock
private SearchUrlHelper.Natives mSearchUrlHelperJniMock;
private BackNavigationTabObserver mBackNavigationTabObserver;
@Before
public void setUp() {
ShadowRecordHistogram.reset();
MockitoAnnotations.initMocks(this);
mMocker.mock(SearchUrlHelperJni.TEST_HOOKS, mSearchUrlHelperJniMock);
mBackNavigationTabObserver = new BackNavigationTabObserver(mTab);
WebContents webContents = mock(WebContents.class);
when(mTab.getWebContents()).thenReturn(webContents);
when(webContents.getNavigationController()).thenReturn(mNavigationController);
GURL searchUrl = JUnitTestGURLs.getGURL(JUnitTestGURLs.SEARCH_URL);
doReturn(true).when(mSearchUrlHelperJniMock).isSrpUrl(eq(searchUrl));
doReturn(true)
.when(mSearchUrlHelperJniMock)
.isSrpUrl(eq(JUnitTestGURLs.getGURL(JUnitTestGURLs.SEARCH_2_URL)));
doReturn(false)
.when(mSearchUrlHelperJniMock)
.isSrpUrl(eq(JUnitTestGURLs.getGURL(JUnitTestGURLs.EXAMPLE_URL)));
doReturn(false)
.when(mSearchUrlHelperJniMock)
.isSrpUrl(eq(JUnitTestGURLs.getGURL(JUnitTestGURLs.RED_1)));
doReturn(false)
.when(mSearchUrlHelperJniMock)
.isSrpUrl(eq(JUnitTestGURLs.getGURL(JUnitTestGURLs.BLUE_1)));
doReturn(true).when(mSearchUrlHelperJniMock).isGoogleDomainUrl(eq(searchUrl));
doReturn(false).when(mSearchUrlHelperJniMock).isGoogleDomainUrl(eq(GURL.emptyGURL()));
}
private NavigationEntry createNavigationEntry(GURL url) {
return new NavigationEntry(
0, url, GURL.emptyGURL(), GURL.emptyGURL(), GURL.emptyGURL(), "", null, 0, 0);
}
private NavigationEntry createNavigationEntry(GURL url, GURL referrer) {
return new NavigationEntry(
0, url, GURL.emptyGURL(), GURL.emptyGURL(), referrer, "", null, 0, 0);
}
private void setNavigationHistory(NavigationEntry entry) {
NavigationHistory history = new NavigationHistory();
history.addEntry(entry);
history.setCurrentEntryIndex(0);
when(mNavigationController.getNavigationHistory()).thenReturn(history);
}
private void navigateThroughEntries(NavigationEntry... entries) {
for (NavigationEntry entry : entries) {
setNavigationHistory(entry);
mBackNavigationTabObserver.onPageLoadFinished(mTab, entry.getUrl());
}
}
@Test
public void testEndSessionWith3PSite() {
navigateThroughEntries(
createNavigationEntry(JUnitTestGURLs.getGURL(JUnitTestGURLs.SEARCH_URL)),
createNavigationEntry(JUnitTestGURLs.getGURL(JUnitTestGURLs.EXAMPLE_URL),
JUnitTestGURLs.getGURL(JUnitTestGURLs.SEARCH_URL)),
createNavigationEntry(JUnitTestGURLs.getGURL(JUnitTestGURLs.SEARCH_URL)),
createNavigationEntry(JUnitTestGURLs.getGURL(JUnitTestGURLs.RED_1),
JUnitTestGURLs.getGURL(JUnitTestGURLs.SEARCH_URL)),
createNavigationEntry(JUnitTestGURLs.getGURL(JUnitTestGURLs.SEARCH_URL)),
createNavigationEntry(JUnitTestGURLs.getGURL(JUnitTestGURLs.BLUE_1)));
assertEquals(1, ShadowRecordHistogram.getHistogramValueCountForTesting(HISTOGRAM, 2));
}
@Test
public void testEndSessionWith3PSite_reload() {
navigateThroughEntries(
createNavigationEntry(JUnitTestGURLs.getGURL(JUnitTestGURLs.SEARCH_URL)),
createNavigationEntry(JUnitTestGURLs.getGURL(JUnitTestGURLs.EXAMPLE_URL),
JUnitTestGURLs.getGURL(JUnitTestGURLs.SEARCH_URL)),
createNavigationEntry(JUnitTestGURLs.getGURL(JUnitTestGURLs.SEARCH_URL)),
createNavigationEntry(JUnitTestGURLs.getGURL(JUnitTestGURLs.SEARCH_URL)),
createNavigationEntry(JUnitTestGURLs.getGURL(JUnitTestGURLs.BLUE_1)));
assertEquals(1, ShadowRecordHistogram.getHistogramValueCountForTesting(HISTOGRAM, 1));
}
@Test
public void testEndSessionWithAnotherSrp() {
navigateThroughEntries(
createNavigationEntry(JUnitTestGURLs.getGURL(JUnitTestGURLs.SEARCH_URL)),
createNavigationEntry(JUnitTestGURLs.getGURL(JUnitTestGURLs.EXAMPLE_URL),
JUnitTestGURLs.getGURL(JUnitTestGURLs.SEARCH_URL)),
createNavigationEntry(JUnitTestGURLs.getGURL(JUnitTestGURLs.SEARCH_URL)),
createNavigationEntry(JUnitTestGURLs.getGURL(JUnitTestGURLs.RED_1),
JUnitTestGURLs.getGURL(JUnitTestGURLs.SEARCH_URL)),
createNavigationEntry(JUnitTestGURLs.getGURL(JUnitTestGURLs.SEARCH_2_URL)));
assertEquals(1, ShadowRecordHistogram.getHistogramValueCountForTesting(HISTOGRAM, 1));
}
@Test
public void testEndSessionWithTabDestroyed() {
navigateThroughEntries(
createNavigationEntry(JUnitTestGURLs.getGURL(JUnitTestGURLs.SEARCH_URL)),
createNavigationEntry(JUnitTestGURLs.getGURL(JUnitTestGURLs.EXAMPLE_URL),
JUnitTestGURLs.getGURL(JUnitTestGURLs.SEARCH_URL)),
createNavigationEntry(JUnitTestGURLs.getGURL(JUnitTestGURLs.SEARCH_URL)),
createNavigationEntry(JUnitTestGURLs.getGURL(JUnitTestGURLs.RED_1),
JUnitTestGURLs.getGURL(JUnitTestGURLs.SEARCH_URL)));
mBackNavigationTabObserver.onDestroyed(mTab);
assertEquals(1, ShadowRecordHistogram.getHistogramValueCountForTesting(HISTOGRAM, 1));
verify(mTab, times(1)).removeObserver(eq(mBackNavigationTabObserver));
}
@Test
public void testEndSessionOnContentChanged() {
navigateThroughEntries(
createNavigationEntry(JUnitTestGURLs.getGURL(JUnitTestGURLs.SEARCH_URL)),
createNavigationEntry(JUnitTestGURLs.getGURL(JUnitTestGURLs.EXAMPLE_URL),
JUnitTestGURLs.getGURL(JUnitTestGURLs.SEARCH_URL)),
createNavigationEntry(JUnitTestGURLs.getGURL(JUnitTestGURLs.SEARCH_URL)),
createNavigationEntry(JUnitTestGURLs.getGURL(JUnitTestGURLs.RED_1),
JUnitTestGURLs.getGURL(JUnitTestGURLs.SEARCH_URL)));
when(mTab.isNativePage()).thenReturn(true);
mBackNavigationTabObserver.onContentChanged(mTab);
assertEquals(1, ShadowRecordHistogram.getHistogramValueCountForTesting(HISTOGRAM, 1));
}
@Test
public void testEndSessionOnHidden() {
navigateThroughEntries(
createNavigationEntry(JUnitTestGURLs.getGURL(JUnitTestGURLs.SEARCH_URL)),
createNavigationEntry(JUnitTestGURLs.getGURL(JUnitTestGURLs.EXAMPLE_URL),
JUnitTestGURLs.getGURL(JUnitTestGURLs.SEARCH_URL)),
createNavigationEntry(JUnitTestGURLs.getGURL(JUnitTestGURLs.SEARCH_URL)),
createNavigationEntry(JUnitTestGURLs.getGURL(JUnitTestGURLs.RED_1),
JUnitTestGURLs.getGURL(JUnitTestGURLs.SEARCH_URL)));
mBackNavigationTabObserver.onHidden(mTab, TabHidingType.ACTIVITY_HIDDEN);
assertEquals(1, ShadowRecordHistogram.getHistogramValueCountForTesting(HISTOGRAM, 1));
}
@Test
public void testRecordSessionWithNoBackNavigation() {
navigateThroughEntries(
createNavigationEntry(JUnitTestGURLs.getGURL(JUnitTestGURLs.SEARCH_URL)),
createNavigationEntry(JUnitTestGURLs.getGURL(JUnitTestGURLs.EXAMPLE_URL),
JUnitTestGURLs.getGURL(JUnitTestGURLs.SEARCH_URL)),
createNavigationEntry(JUnitTestGURLs.getGURL(JUnitTestGURLs.BLUE_1)));
assertEquals(1, ShadowRecordHistogram.getHistogramValueCountForTesting(HISTOGRAM, 0));
}
@Test
public void testNotRecordWhenNotSeenSrp() {
navigateThroughEntries(
createNavigationEntry(JUnitTestGURLs.getGURL(JUnitTestGURLs.EXAMPLE_URL)),
createNavigationEntry(JUnitTestGURLs.getGURL(JUnitTestGURLs.BLUE_1)));
assertEquals(0, ShadowRecordHistogram.getHistogramValueCountForTesting(HISTOGRAM, 0));
}
}
......@@ -18,6 +18,26 @@
namespace continuous_search {
jboolean JNI_SearchUrlHelper_IsGoogleDomainUrl(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& j_gurl) {
std::unique_ptr<GURL> url = url::GURLAndroid::ToNativeGURL(env, j_gurl);
if (!url->is_valid())
return false;
return static_cast<jboolean>(
google_util::IsGoogleDomainUrl(*url, google_util::DISALLOW_SUBDOMAIN,
google_util::DISALLOW_NON_STANDARD_PORTS));
}
jboolean JNI_SearchUrlHelper_IsSrpUrl(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& j_gurl) {
std::unique_ptr<GURL> url = url::GURLAndroid::ToNativeGURL(env, j_gurl);
if (!url->is_valid())
return false;
return static_cast<jboolean>(google_util::IsGoogleSearchUrl(*url));
}
base::android::ScopedJavaLocalRef<jstring> JNI_SearchUrlHelper_GetQueryIfSrpUrl(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& j_gurl) {
......
......@@ -52,6 +52,21 @@ reviews. Googlers can read more about this at go/gwsq-gerrit.
</summary>
</histogram>
<histogram name="Browser.ContinuousSearch.BackNavigationToSrp" units="count"
expires_after="2021-06-20">
<owner>ckitagawa@chromium.org</owner>
<owner>fredmello@chromium.org</owner>
<owner>mahmoudi@chromium.org</owner>
<owner>yashard@chromium.org</owner>
<summary>
Records the number of back navigations to the Google search result page
while in a search session. It is emitted when the session is considered
ended. Different actions that cause the search session to end include
navigating away from SRP (or one on the pages linked in SRP), closing the
SRP tab and closing the browser.
</summary>
</histogram>
<histogram name="Browser.DarkModeStatus" enum="DarkModeStatus"
expires_after="2021-06-27">
<owner>lgrey@chromium.org</owner>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment