// Copyright 2018 The Chromium Authors
// 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.customtabs;
import static org.chromium.components.content_settings.PrefNames.COOKIE_CONTROLS_MODE;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import androidx.browser.customtabs.CustomTabsCallback;
import androidx.browser.customtabs.CustomTabsIntent;
import androidx.browser.customtabs.CustomTabsService;
import androidx.browser.customtabs.CustomTabsSession;
import androidx.browser.customtabs.CustomTabsSessionToken;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.ThreadUtils;
import org.chromium.base.task.PostTask;
import org.chromium.base.task.TaskTraits;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.base.test.util.DisabledTest;
import org.chromium.base.test.util.Features.DisableFeatures;
import org.chromium.base.test.util.Features.EnableFeatures;
import org.chromium.base.test.util.HistogramWatcher;
import org.chromium.chrome.browser.MockSafeBrowsingApiHandler;
import org.chromium.chrome.browser.browserservices.verification.ChromeOriginVerifier;
import org.chromium.chrome.browser.document.ChromeLauncherActivity;
import org.chromium.chrome.browser.firstrun.FirstRunStatus;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.profiles.ProfileManager;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.components.content_settings.CookieControlsMode;
import org.chromium.components.embedder_support.util.Origin;
import org.chromium.components.prefs.PrefService;
import org.chromium.components.safe_browsing.SafeBrowsingApiBridge;
import org.chromium.components.user_prefs.UserPrefs;
import org.chromium.content_public.browser.test.util.JavaScriptUtils;
import org.chromium.net.NetError;
import org.chromium.net.test.EmbeddedTestServer;
import org.chromium.net.test.ServerCertificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/** Tests for detached resource requests. */
@RunWith(ChromeJUnit4ClassRunner.class)
@EnableFeatures(ChromeFeatureList.SAFE_BROWSING_DELAYED_WARNINGS)
public class DetachedResourceRequestTest {
@Rule
public CustomTabActivityTestRule mCustomTabActivityTestRule = new CustomTabActivityTestRule();
private CustomTabsConnection mConnection;
private Context mContext;
private EmbeddedTestServer mServer;
private static final Uri ORIGIN = Uri.parse("http://cats.google.com");
private static final int NET_OK = 0;
@Before
public void setUp() {
ThreadUtils.runOnUiThreadBlocking(() -> FirstRunStatus.setFirstRunFlowComplete(true));
mConnection = CustomTabsTestUtils.setUpConnection();
mContext =
InstrumentationRegistry.getInstrumentation()
.getTargetContext()
.getApplicationContext();
ThreadUtils.runOnUiThreadBlocking(ChromeOriginVerifier::clearCachedVerificationsForTesting);
}
@After
public void tearDown() {
CustomTabsTestUtils.cleanupSessions(mConnection);
}
@Test
@SmallTest
public void testCanDoParallelRequest() {
CustomTabsSessionToken session = CustomTabsSessionToken.createMockSessionTokenForTesting();
Assert.assertTrue(mConnection.newSession(session));
ThreadUtils.runOnUiThreadBlocking(
() -> Assert.assertFalse(mConnection.canDoParallelRequest(session, ORIGIN)));
ThreadUtils.runOnUiThreadBlocking(
() -> {
String packageName = mContext.getPackageName();
ChromeOriginVerifier.addVerificationOverride(
packageName,
Origin.create(ORIGIN.toString()),
CustomTabsService.RELATION_USE_AS_ORIGIN);
Assert.assertTrue(mConnection.canDoParallelRequest(session, ORIGIN));
});
}
@Test
@SmallTest
public void testCanDoResourcePrefetch() throws Exception {
CustomTabsSessionToken session = CustomTabsSessionToken.createMockSessionTokenForTesting();
Assert.assertTrue(mConnection.newSession(session));
ThreadUtils.runOnUiThreadBlocking(
() -> {
String packageName = mContext.getPackageName();
ChromeOriginVerifier.addVerificationOverride(
packageName,
Origin.create(ORIGIN.toString()),
CustomTabsService.RELATION_USE_AS_ORIGIN);
});
Intent intent =
prepareIntentForResourcePrefetch(
Arrays.asList(Uri.parse("https://foo.bar")), ORIGIN);
ThreadUtils.runOnUiThreadBlocking(
() -> Assert.assertEquals(0, mConnection.maybePrefetchResources(session, intent)));
CustomTabsTestUtils.warmUpAndWait();
ThreadUtils.runOnUiThreadBlocking(
() -> Assert.assertEquals(0, mConnection.maybePrefetchResources(session, intent)));
mConnection.mClientManager.setAllowResourcePrefetchForSession(session, true);
ThreadUtils.runOnUiThreadBlocking(
() -> Assert.assertEquals(1, mConnection.maybePrefetchResources(session, intent)));
}
@Test
@SmallTest
public void testStartParallelRequestValidation() throws Exception {
CustomTabsSessionToken session = prepareSession();
CustomTabsTestUtils.warmUpAndWait();
ThreadUtils.runOnUiThreadBlocking(
() -> {
int expected = CustomTabsConnection.ParallelRequestStatus.NO_REQUEST;
var histogram =
HistogramWatcher.newSingleRecordWatcher(
"CustomTabs.ParallelRequestStatusOnStart", expected);
Assert.assertEquals(
expected, mConnection.handleParallelRequest(session, new Intent()));
histogram.assertExpected();
expected = CustomTabsConnection.ParallelRequestStatus.FAILURE_INVALID_URL;
histogram =
HistogramWatcher.newSingleRecordWatcher(
"CustomTabs.ParallelRequestStatusOnStart", expected);
Intent intent =
prepareIntent(
Uri.parse("android-app://this.is.an.android.app"), ORIGIN);
Assert.assertEquals(
"Should not allow android-app:// scheme",
expected,
mConnection.handleParallelRequest(session, intent));
histogram.assertExpected();
expected = CustomTabsConnection.ParallelRequestStatus.FAILURE_INVALID_URL;
histogram =
HistogramWatcher.newSingleRecordWatcher(
"CustomTabs.ParallelRequestStatusOnStart", expected);
intent = prepareIntent(Uri.parse(""), ORIGIN);
Assert.assertEquals(
"Should not allow an empty URL",
expected,
mConnection.handleParallelRequest(session, intent));
histogram.assertExpected();
expected =
CustomTabsConnection.ParallelRequestStatus
.FAILURE_INVALID_REFERRER_FOR_SESSION;
histogram =
HistogramWatcher.newSingleRecordWatcher(
"CustomTabs.ParallelRequestStatusOnStart", expected);
intent =
prepareIntent(
Uri.parse("HTTPS://foo.bar"), Uri.parse("wrong://origin"));
Assert.assertEquals(
"Should not allow an arbitrary origin",
expected,
mConnection.handleParallelRequest(session, intent));
histogram.assertExpected();
expected = CustomTabsConnection.ParallelRequestStatus.SUCCESS;
histogram =
HistogramWatcher.newSingleRecordWatcher(
"CustomTabs.ParallelRequestStatusOnStart", expected);
intent = prepareIntent(Uri.parse("HTTPS://foo.bar"), ORIGIN);
Assert.assertEquals(
expected, mConnection.handleParallelRequest(session, intent));
histogram.assertExpected();
});
}
@Test
@SmallTest
public void testStartResourcePrefetchUrlsValidation() throws Exception {
CustomTabsSessionToken session = prepareSession();
CustomTabsTestUtils.warmUpAndWait();
ThreadUtils.runOnUiThreadBlocking(
() -> {
Assert.assertEquals(
0, mConnection.maybePrefetchResources(session, new Intent()));
ArrayList<Uri> urls = new ArrayList<>();
Intent intent = prepareIntentForResourcePrefetch(urls, ORIGIN);
Assert.assertEquals(0, mConnection.maybePrefetchResources(session, intent));
urls.add(Uri.parse("android-app://this.is.an.android.app"));
intent = prepareIntentForResourcePrefetch(urls, ORIGIN);
Assert.assertEquals(0, mConnection.maybePrefetchResources(session, intent));
urls.add(Uri.parse(""));
intent = prepareIntentForResourcePrefetch(urls, ORIGIN);
Assert.assertEquals(0, mConnection.maybePrefetchResources(session, intent));
urls.add(Uri.parse("https://foo.bar"));
intent = prepareIntentForResourcePrefetch(urls, ORIGIN);
Assert.assertEquals(1, mConnection.maybePrefetchResources(session, intent));
urls.add(Uri.parse("https://bar.foo"));
intent = prepareIntentForResourcePrefetch(urls, ORIGIN);
Assert.assertEquals(2, mConnection.maybePrefetchResources(session, intent));
intent = prepareIntentForResourcePrefetch(urls, Uri.parse("wrong://origin"));
Assert.assertEquals(0, mConnection.maybePrefetchResources(session, intent));
});
}
@Test
@SmallTest
@EnableFeatures(ChromeFeatureList.CCT_REPORT_PARALLEL_REQUEST_STATUS)
public void testCanStartParallelRequest() throws Exception {
testCanStartParallelRequest(true);
}
@Test
@SmallTest
@EnableFeatures(ChromeFeatureList.CCT_REPORT_PARALLEL_REQUEST_STATUS)
public void testCanStartParallelRequestBeforeNative() throws Exception {
testCanStartParallelRequest(false);
}
@Test
@SmallTest
@EnableFeatures(ChromeFeatureList.CCT_REPORT_PARALLEL_REQUEST_STATUS)
public void testParallelRequestFailureCallback() throws Exception {
Uri url = Uri.parse("http://request-url");
int status =
CustomTabsConnection.ParallelRequestStatus.FAILURE_INVALID_REFERRER_FOR_SESSION;
DetachedResourceRequestCheckCallback customTabsCallback =
new DetachedResourceRequestCheckCallback(url, status, 0);
CustomTabsSessionToken session = prepareSession(ORIGIN, customTabsCallback);
CustomTabsTestUtils.warmUpAndWait();
PostTask.runOrPostTask(
TaskTraits.UI_DEFAULT,
() -> {
Assert.assertEquals(
status,
mConnection.handleParallelRequest(
session,
prepareIntent(url, Uri.parse("http://not-the-right-origin"))));
});
customTabsCallback.waitForRequest(0, 1);
}
@Test
@SmallTest
@EnableFeatures(ChromeFeatureList.CCT_REPORT_PARALLEL_REQUEST_STATUS)
public void testParallelRequestCompletionFailureCallback() throws Exception {
final CallbackHelper cb = new CallbackHelper();
setUpTestServerWithListener(
new EmbeddedTestServer.ConnectionListener() {
@Override
public void readFromSocket(long socketId) {
cb.notifyCalled();
}
});
Uri url = Uri.parse(mServer.getURL("/close-socket"));
DetachedResourceRequestCheckCallback customTabsCallback =
new DetachedResourceRequestCheckCallback(
url,
CustomTabsConnection.ParallelRequestStatus.SUCCESS,
Math.abs(NetError.ERR_EMPTY_RESPONSE));
CustomTabsSessionToken session = prepareSession(ORIGIN, customTabsCallback);
PostTask.runOrPostTask(
TaskTraits.UI_DEFAULT,
() -> mConnection.onHandledIntent(session, prepareIntent(url, ORIGIN)));
CustomTabsTestUtils.warmUpAndWait();
customTabsCallback.waitForRequest(0, 1);
cb.waitForCallback(0, 1);
customTabsCallback.waitForCompletion(0, 1);
}
@Test
@SmallTest
public void testCanStartResourcePrefetch() throws Exception {
CustomTabsSessionToken session = prepareSession();
CustomTabsTestUtils.warmUpAndWait();
final CallbackHelper cb = new CallbackHelper();
// We expect one read per prefetched url.
setUpTestServerWithListener(
new EmbeddedTestServer.ConnectionListener() {
@Override
public void readFromSocket(long socketId) {
cb.notifyCalled();
}
});
List<Uri> urls =
Arrays.asList(
Uri.parse(mServer.getURL("/echo-raw?a=1")),
Uri.parse(mServer.getURL("/echo-raw?a=2")),
Uri.parse(mServer.getURL("/echo-raw?a=3")));
PostTask.runOrPostTask(
TaskTraits.UI_DEFAULT,
() -> {
Assert.assertEquals(
urls.size(),
mConnection.maybePrefetchResources(
session, prepareIntentForResourcePrefetch(urls, ORIGIN)));
});
cb.waitForCallback(0, urls.size());
}
@Test
@SmallTest
@EnableFeatures(ChromeFeatureList.CCT_REPORT_PARALLEL_REQUEST_STATUS)
@DisableFeatures(ChromeFeatureList.TRACKING_PROTECTION_3PCD)
public void testCanSetCookie() throws Exception {
testCanSetCookie(true);
}
@Test
@SmallTest
@EnableFeatures(ChromeFeatureList.CCT_REPORT_PARALLEL_REQUEST_STATUS)
@DisableFeatures(ChromeFeatureList.TRACKING_PROTECTION_3PCD)
public void testCanSetCookieBeforeNative() throws Exception {
testCanSetCookie(false);
}
/**
* Tests that cached detached resource requests that are forbidden by SafeBrowsing don't end up
* in the content area, for a main resource.
*/
@Test
@SmallTest
@DisableFeatures(ChromeFeatureList.SPLIT_CACHE_BY_NETWORK_ISOLATION_KEY)
@DisabledTest(message = "https://crbug.com/1431268")
public void testSafeBrowsingMainResource() throws Exception {
testSafeBrowsingMainResource(/* afterNative= */ true, /* splitCacheEnabled= */ false);
}
/**
* Tests that non-cached detached resource requests that are forbidden by SafeBrowsing don't end
* up in the content area, for a main resource.
*/
@Test
@SmallTest
@EnableFeatures(ChromeFeatureList.SPLIT_CACHE_BY_NETWORK_ISOLATION_KEY)
@DisabledTest(message = "Flaky. See crbug.com/1523239")
public void testSafeBrowsingMainResourceWithSplitCache() throws Exception {
testSafeBrowsingMainResource(/* afterNative= */ true, /* splitCacheEnabled= */ true);
}
/**
* Tests that cached detached resource requests that are forbidden by SafeBrowsing don't end up
* in the content area, for a main resource.
*/
@Test
@SmallTest
@DisableFeatures(ChromeFeatureList.SPLIT_CACHE_BY_NETWORK_ISOLATION_KEY)
@DisabledTest(message = "https://crbug.com/1431268")
public void testSafeBrowsingMainResourceBeforeNative() throws Exception {
testSafeBrowsingMainResource(/* afterNative= */ false, /* splitCacheEnabled= */ false);
}
@Test
@SmallTest
@EnableFeatures(ChromeFeatureList.CCT_REPORT_PARALLEL_REQUEST_STATUS)
public void testCanBlockThirdPartyCookies() throws Exception {
CustomTabsTestUtils.warmUpAndWait();
mServer = EmbeddedTestServer.createAndStartHTTPSServer(mContext, ServerCertificate.CERT_OK);
ThreadUtils.runOnUiThreadBlocking(
() -> {
PrefService prefs = UserPrefs.get(ProfileManager.getLastUsedRegularProfile());
Assert.assertEquals(
prefs.getInteger(COOKIE_CONTROLS_MODE),
CookieControlsMode.INCOGNITO_ONLY);
prefs.setInteger(COOKIE_CONTROLS_MODE, CookieControlsMode.BLOCK_THIRD_PARTY);
});
final Uri url = Uri.parse(mServer.getURL("/set-cookie?acookie;SameSite=none;Secure"));
DetachedResourceRequestCheckCallback customTabsCallback =
new DetachedResourceRequestCheckCallback(
url, CustomTabsConnection.ParallelRequestStatus.SUCCESS, NET_OK);
CustomTabsSessionToken session = prepareSession(ORIGIN, customTabsCallback);
ThreadUtils.runOnUiThreadBlocking(
() -> {
Assert.assertEquals(
CustomTabsConnection.ParallelRequestStatus.SUCCESS,
mConnection.handleParallelRequest(session, prepareIntent(url, ORIGIN)));
});
customTabsCallback.waitForRequest(0, 1);
customTabsCallback.waitForCompletion(0, 1);
String echoUrl = mServer.getURL("/echoheader?Cookie");
Intent intent = CustomTabsIntentTestUtils.createMinimalCustomTabIntent(mContext, echoUrl);
mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);
Tab tab = mCustomTabActivityTestRule.getActivity().getActivityTab();
String content =
JavaScriptUtils.executeJavaScriptAndWaitForResult(
tab.getWebContents(), "document.body.textContent");
Assert.assertEquals("\"None\"", content);
}
/**
* Demonstrates that cookies are SameSite=Lax by default, and cookies in third-party contexts
* require both SameSite=None and Secure.
*/
@Test
@SmallTest
@EnableFeatures(ChromeFeatureList.CCT_REPORT_PARALLEL_REQUEST_STATUS)
@DisableFeatures(ChromeFeatureList.TRACKING_PROTECTION_3PCD)
public void testSameSiteLaxByDefaultCookies() throws Exception {
CustomTabsTestUtils.warmUpAndWait();
mServer = EmbeddedTestServer.createAndStartHTTPSServer(mContext, ServerCertificate.CERT_OK);
// This isn't blocking third-party cookies by preferences.
ThreadUtils.runOnUiThreadBlocking(
() -> {
PrefService prefs = UserPrefs.get(ProfileManager.getLastUsedRegularProfile());
Assert.assertEquals(
prefs.getInteger(COOKIE_CONTROLS_MODE),
CookieControlsMode.INCOGNITO_ONLY);
});
// Of the three cookies, only one that's both SameSite=None and Secure
// is actually set. (And Secure is meant as the attribute, being over
// https isn't enough).
final Uri url =
Uri.parse(
mServer.getURL(
"/set-cookie?a=1&b=2;SameSite=None&c=3;SameSite=None;Secure;"));
DetachedResourceRequestCheckCallback customTabsCallback =
new DetachedResourceRequestCheckCallback(
url, CustomTabsConnection.ParallelRequestStatus.SUCCESS, NET_OK);
CustomTabsSessionToken session = prepareSession(ORIGIN, customTabsCallback);
ThreadUtils.runOnUiThreadBlocking(
() -> {
Assert.assertEquals(
CustomTabsConnection.ParallelRequestStatus.SUCCESS,
mConnection.handleParallelRequest(session, prepareIntent(url, ORIGIN)));
});
customTabsCallback.waitForRequest(0, 1);
customTabsCallback.waitForCompletion(0, 1);
String echoUrl = mServer.getURL("/echoheader?Cookie");
Intent intent = CustomTabsIntentTestUtils.createMinimalCustomTabIntent(mContext, echoUrl);
mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);
Tab tab = mCustomTabActivityTestRule.getActivity().getActivityTab();
String content =
JavaScriptUtils.executeJavaScriptAndWaitForResult(
tab.getWebContents(), "document.body.textContent");
Assert.assertEquals("\"c=3\"", content);
}
@Test
@SmallTest
@EnableFeatures(ChromeFeatureList.CCT_REPORT_PARALLEL_REQUEST_STATUS)
public void testThirdPartyCookieBlockingAllowsFirstParty() throws Exception {
CustomTabsTestUtils.warmUpAndWait();
mServer = EmbeddedTestServer.createAndStartServer(mContext);
ThreadUtils.runOnUiThreadBlocking(
() -> {
PrefService prefs = UserPrefs.get(ProfileManager.getLastUsedRegularProfile());
Assert.assertEquals(
prefs.getInteger(COOKIE_CONTROLS_MODE),
CookieControlsMode.INCOGNITO_ONLY);
prefs.setInteger(COOKIE_CONTROLS_MODE, CookieControlsMode.BLOCK_THIRD_PARTY);
});
final Uri url = Uri.parse(mServer.getURL("/set-cookie?acookie"));
final Uri origin = Uri.parse(Origin.create(url).toString());
DetachedResourceRequestCheckCallback customTabsCallback =
new DetachedResourceRequestCheckCallback(
url, CustomTabsConnection.ParallelRequestStatus.SUCCESS, NET_OK);
CustomTabsSessionToken session = prepareSession(origin, customTabsCallback);
ThreadUtils.runOnUiThreadBlocking(
() -> {
Assert.assertEquals(
CustomTabsConnection.ParallelRequestStatus.SUCCESS,
mConnection.handleParallelRequest(session, prepareIntent(url, origin)));
});
customTabsCallback.waitForRequest(0, 1);
customTabsCallback.waitForCompletion(0, 1);
String echoUrl = mServer.getURL("/echoheader?Cookie");
Intent intent = CustomTabsIntentTestUtils.createMinimalCustomTabIntent(mContext, echoUrl);
mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);
Tab tab = mCustomTabActivityTestRule.getActivity().getActivityTab();
String content =
JavaScriptUtils.executeJavaScriptAndWaitForResult(
tab.getWebContents(), "document.body.textContent");
Assert.assertEquals("\"acookie\"", content);
}
@Test
@SmallTest
@EnableFeatures(ChromeFeatureList.CCT_REPORT_PARALLEL_REQUEST_STATUS)
public void testRepeatedIntents() throws Exception {
mServer = EmbeddedTestServer.createAndStartServer(mContext);
final Uri url = Uri.parse(mServer.getURL("/set-cookie?acookie"));
DetachedResourceRequestCheckCallback callback =
new DetachedResourceRequestCheckCallback(
url, CustomTabsConnection.ParallelRequestStatus.SUCCESS, NET_OK);
CustomTabsSession session = CustomTabsTestUtils.bindWithCallback(callback).session;
Uri launchedUrl = Uri.parse(mServer.getURL("/echotitle"));
Intent intent = (new CustomTabsIntent.Builder(session).build()).intent;
intent.setComponent(new ComponentName(mContext, ChromeLauncherActivity.class));
intent.setAction(Intent.ACTION_VIEW);
intent.setData(launchedUrl);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(CustomTabsConnection.PARALLEL_REQUEST_URL_KEY, url);
intent.putExtra(CustomTabsConnection.PARALLEL_REQUEST_REFERRER_KEY, ORIGIN);
CustomTabsSessionToken token = CustomTabsSessionToken.getSessionTokenFromIntent(intent);
Assert.assertTrue(mConnection.newSession(token));
mConnection.mClientManager.setAllowParallelRequestForSession(token, true);
ThreadUtils.runOnUiThreadBlocking(
() -> {
ChromeOriginVerifier.addVerificationOverride(
mContext.getPackageName(),
Origin.create(ORIGIN.toString()),
CustomTabsService.RELATION_USE_AS_ORIGIN);
Assert.assertTrue(mConnection.canDoParallelRequest(token, ORIGIN));
});
// Launching a CCT and loading a URL takes more time than usual. Gives a longer timeout.
// See crbug.com/40737671.
mContext.startActivity(intent);
callback.waitForRequest(0, 1, 10, TimeUnit.SECONDS);
callback.waitForCompletion(0, 1, 10, TimeUnit.SECONDS);
mContext.startActivity(intent);
callback.waitForRequest(1, 1, 10, TimeUnit.SECONDS);
callback.waitForCompletion(1, 1, 10, TimeUnit.SECONDS);
}
private void testCanStartParallelRequest(boolean afterNative) throws Exception {
final CallbackHelper cb = new CallbackHelper();
setUpTestServerWithListener(
new EmbeddedTestServer.ConnectionListener() {
@Override
public void readFromSocket(long socketId) {
cb.notifyCalled();
}
});
Uri url = Uri.parse(mServer.getURL("/echotitle"));
DetachedResourceRequestCheckCallback customTabsCallback =
new DetachedResourceRequestCheckCallback(
url, CustomTabsConnection.ParallelRequestStatus.SUCCESS, NET_OK);
CustomTabsSessionToken session = prepareSession(ORIGIN, customTabsCallback);
if (afterNative) CustomTabsTestUtils.warmUpAndWait();
PostTask.runOrPostTask(
TaskTraits.UI_DEFAULT,
() -> mConnection.onHandledIntent(session, prepareIntent(url, ORIGIN)));
if (!afterNative) CustomTabsTestUtils.warmUpAndWait();
customTabsCallback.waitForRequest(0, 1);
cb.waitForCallback(0, 1);
customTabsCallback.waitForCompletion(0, 1);
}
private void testCanSetCookie(boolean afterNative) throws Exception {
mServer = EmbeddedTestServer.createAndStartHTTPSServer(mContext, ServerCertificate.CERT_OK);
final Uri url = Uri.parse(mServer.getURL("/set-cookie?acookie;SameSite=none;Secure"));
DetachedResourceRequestCheckCallback customTabsCallback =
new DetachedResourceRequestCheckCallback(
url, CustomTabsConnection.ParallelRequestStatus.SUCCESS, NET_OK);
CustomTabsSessionToken session = prepareSession(ORIGIN, customTabsCallback);
if (afterNative) CustomTabsTestUtils.warmUpAndWait();
ThreadUtils.runOnUiThreadBlocking(
() -> mConnection.onHandledIntent(session, prepareIntent(url, ORIGIN)));
if (!afterNative) CustomTabsTestUtils.warmUpAndWait();
customTabsCallback.waitForRequest(0, 1);
customTabsCallback.waitForCompletion(0, 1);
String echoUrl = mServer.getURL("/echoheader?Cookie");
Intent intent = CustomTabsIntentTestUtils.createMinimalCustomTabIntent(mContext, echoUrl);
mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);
Tab tab = mCustomTabActivityTestRule.getActivity().getActivityTab();
String content =
JavaScriptUtils.executeJavaScriptAndWaitForResult(
tab.getWebContents(), "document.body.textContent");
Assert.assertEquals("\"acookie\"", content);
}
private void testSafeBrowsingMainResource(boolean afterNative, boolean splitCacheEnabled)
throws Exception {
SafeBrowsingApiBridge.setSafeBrowsingApiHandler(new MockSafeBrowsingApiHandler());
CustomTabsSessionToken session = prepareSession();
String cacheable = "/cachetime";
CallbackHelper readFromSocketCallback =
waitForDetachedRequest(session, cacheable, afterNative);
Uri url = Uri.parse(mServer.getURL(cacheable));
try {
MockSafeBrowsingApiHandler.addMockResponse(
url.toString(), MockSafeBrowsingApiHandler.SOCIAL_ENGINEERING_CODE);
Intent intent =
CustomTabsIntentTestUtils.createMinimalCustomTabIntent(
mContext, url.toString());
mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);
Tab tab = mCustomTabActivityTestRule.getActivity().getActivityTab();
// TODO(crbug.com/40666836): For now, we check the presence of an interstitial through
// the title since isShowingInterstitialPage does not work with committed interstitials.
// Once we fully migrate to committed interstitials, this should be changed to a more
// robust check.
CriteriaHelper.pollUiThread(
() -> tab.getWebContents().getTitle().equals("Security error"));
if (splitCacheEnabled) {
// Note that since the SplitCacheByNetworkIsolationKey feature is
// enabled, the detached request and the original request both
// would be read from the socket as they both would have different
// top frame origins in the cache partitioning key: |ORIGIN| and
// mServer's base url, respectively.
Assert.assertEquals(2, readFromSocketCallback.getCallCount());
} else {
// 1 read from the detached request, and 0 from the page load, as
// the response comes from the cache, and SafeBrowsing blocks it.
Assert.assertEquals(1, readFromSocketCallback.getCallCount());
}
} finally {
MockSafeBrowsingApiHandler.clearMockResponses();
SafeBrowsingApiBridge.clearHandlerForTesting();
}
}
private CustomTabsSessionToken prepareSession() throws Exception {
return prepareSession(ORIGIN, null);
}
private CustomTabsSessionToken prepareSession(Uri origin) throws Exception {
return prepareSession(origin, null);
}
private CustomTabsSessionToken prepareSession(Uri origin, CustomTabsCallback callback)
throws Exception {
CustomTabsSession session = CustomTabsTestUtils.bindWithCallback(callback).session;
Intent intent = (new CustomTabsIntent.Builder(session)).build().intent;
CustomTabsSessionToken token = CustomTabsSessionToken.getSessionTokenFromIntent(intent);
Assert.assertTrue(mConnection.newSession(token));
mConnection.mClientManager.setAllowParallelRequestForSession(token, true);
mConnection.mClientManager.setAllowResourcePrefetchForSession(token, true);
ThreadUtils.runOnUiThreadBlocking(
() -> {
ChromeOriginVerifier.addVerificationOverride(
mContext.getPackageName(),
Origin.create(origin.toString()),
CustomTabsService.RELATION_USE_AS_ORIGIN);
Assert.assertTrue(mConnection.canDoParallelRequest(token, origin));
});
return token;
}
private void setUpTestServerWithListener(EmbeddedTestServer.ConnectionListener listener) {
mServer = new EmbeddedTestServer();
final CallbackHelper readFromSocketCallback = new CallbackHelper();
mServer.initializeNative(mContext, EmbeddedTestServer.ServerHTTPSSetting.USE_HTTP);
mServer.setConnectionListener(listener);
mServer.addDefaultHandlers("");
Assert.assertTrue(mServer.start());
}
private CallbackHelper waitForDetachedRequest(
CustomTabsSessionToken session, String relativeUrl, boolean afterNative)
throws TimeoutException {
// Count the number of times data is read from the socket.
// We expect 1 for the detached request.
// Cannot count connections as Chrome opens multiple sockets at page load time.
CallbackHelper readFromSocketCallback = new CallbackHelper();
setUpTestServerWithListener(
new EmbeddedTestServer.ConnectionListener() {
@Override
public void readFromSocket(long socketId) {
readFromSocketCallback.notifyCalled();
}
});
Uri url = Uri.parse(mServer.getURL(relativeUrl));
if (afterNative) CustomTabsTestUtils.warmUpAndWait();
ThreadUtils.runOnUiThreadBlocking(
() -> mConnection.onHandledIntent(session, prepareIntent(url, ORIGIN)));
if (!afterNative) CustomTabsTestUtils.warmUpAndWait();
readFromSocketCallback.waitForCallback(0);
return readFromSocketCallback;
}
private static Intent prepareIntent(Uri url, Uri referrer) {
Intent intent = new Intent();
intent.setData(Uri.parse("http://www.example.com"));
intent.putExtra(CustomTabsConnection.PARALLEL_REQUEST_URL_KEY, url);
intent.putExtra(CustomTabsConnection.PARALLEL_REQUEST_REFERRER_KEY, referrer);
return intent;
}
private static Intent prepareIntentForResourcePrefetch(List<Uri> urls, Uri referrer) {
Intent intent = new Intent();
intent.setData(Uri.parse("http://www.example.com"));
intent.putExtra(CustomTabsConnection.RESOURCE_PREFETCH_URL_LIST_KEY, new ArrayList<>(urls));
intent.putExtra(CustomTabsConnection.PARALLEL_REQUEST_REFERRER_KEY, referrer);
return intent;
}
private static class DetachedResourceRequestCheckCallback extends CustomTabsCallback {
private final Uri mExpectedUrl;
private final int mExpectedRequestStatus;
private final int mExpectedFinalStatus;
private final CallbackHelper mRequestedWaiter = new CallbackHelper();
private final CallbackHelper mCompletionWaiter = new CallbackHelper();
public DetachedResourceRequestCheckCallback(
Uri expectedUrl, int expectedRequestStatus, int expectedFinalStatus) {
super();
mExpectedUrl = expectedUrl;
mExpectedRequestStatus = expectedRequestStatus;
mExpectedFinalStatus = expectedFinalStatus;
}
@Override
public void extraCallback(String callbackName, Bundle args) {
if (CustomTabsConnection.ON_DETACHED_REQUEST_REQUESTED.equals(callbackName)) {
Uri url = args.getParcelable("url");
int status = args.getInt("status");
Assert.assertEquals(mExpectedUrl, url);
Assert.assertEquals(mExpectedRequestStatus, status);
mRequestedWaiter.notifyCalled();
} else if (CustomTabsConnection.ON_DETACHED_REQUEST_COMPLETED.equals(callbackName)) {
Uri url = args.getParcelable("url");
int status = args.getInt("net_error");
Assert.assertEquals(mExpectedUrl, url);
Assert.assertEquals(mExpectedFinalStatus, status);
mCompletionWaiter.notifyCalled();
}
}
public void waitForRequest() throws TimeoutException {
mRequestedWaiter.waitForOnly();
}
public void waitForRequest(int currentCallCount, int numberOfCallsToWaitFor)
throws TimeoutException {
mRequestedWaiter.waitForCallback(currentCallCount, numberOfCallsToWaitFor);
}
public void waitForRequest(int currentCount, int expectCount, int timeout, TimeUnit unit)
throws TimeoutException {
mRequestedWaiter.waitForCallback(currentCount, expectCount, timeout, unit);
}
public void waitForCompletion() throws TimeoutException {
mCompletionWaiter.waitForOnly();
}
public void waitForCompletion(int currentCallCount, int numberOfCallsToWaitFor)
throws TimeoutException {
mCompletionWaiter.waitForCallback(currentCallCount, numberOfCallsToWaitFor);
}
public void waitForCompletion(int currentCount, int expectCount, int timeout, TimeUnit unit)
throws TimeoutException {
mCompletionWaiter.waitForCallback(currentCount, expectCount, timeout, unit);
}
}
}