Commit 1989515c authored by Benoit Lize's avatar Benoit Lize Committed by Commit Bot

customtabs: Add CustomTabs APIs for detached parallel requests.

This CL adds the CustomTabs "client" APIs for parallel request:
- Enable and query parallel request state
- Get the URL and requested origin from the Intent

Bug: 816837
Change-Id: I6c5cc86453a941f08f118be6229bf3a2c497d344
Reviewed-on: https://chromium-review.googlesource.com/962761Reviewed-by: default avatarBernhard Bauer <bauerb@chromium.org>
Commit-Queue: Benoit L <lizeb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#543122}
parent 88b034e5
...@@ -147,6 +147,7 @@ class ClientManager { ...@@ -147,6 +147,7 @@ class ClientManager {
private String mPredictedUrl; private String mPredictedUrl;
private long mLastMayLaunchUrlTimestamp; private long mLastMayLaunchUrlTimestamp;
private int mSpeculationMode; private int mSpeculationMode;
private boolean mAllowParallelRequest;
public SessionParams(Context context, int uid, DisconnectCallback callback, public SessionParams(Context context, int uid, DisconnectCallback callback,
PostMessageHandler postMessageHandler) { PostMessageHandler postMessageHandler) {
...@@ -616,6 +617,17 @@ class ClientManager { ...@@ -616,6 +617,17 @@ class ClientManager {
: params.mSpeculationMode; : params.mSpeculationMode;
} }
public synchronized void setAllowParallelRequestForSession(
CustomTabsSessionToken session, boolean allowed) {
SessionParams params = mSessionParams.get(session);
if (params != null) params.mAllowParallelRequest = allowed;
}
public synchronized boolean getAllowParallelRequestForSession(CustomTabsSessionToken session) {
SessionParams params = mSessionParams.get(session);
return params != null ? params.mAllowParallelRequest : false;
}
/** /**
* Returns whether an origin is first-party with respect to a session, that is if the * Returns whether an origin is first-party with respect to a session, that is if the
* application linked to the session has a relation with the provided origin. This does not * application linked to the session has a relation with the provided origin. This does not
......
...@@ -147,6 +147,12 @@ public class CustomTabsConnection { ...@@ -147,6 +147,12 @@ public class CustomTabsConnection {
// TODO(lizeb): Move to the support library. // TODO(lizeb): Move to the support library.
@VisibleForTesting @VisibleForTesting
static final String REDIRECT_ENDPOINT_KEY = "android.support.customtabs.REDIRECT_ENDPOINT"; static final String REDIRECT_ENDPOINT_KEY = "android.support.customtabs.REDIRECT_ENDPOINT";
@VisibleForTesting
static final String PARALLEL_REQUEST_REFERRER_KEY =
"android.support.customtabs.PARALLEL_REQUEST_REFERRER";
@VisibleForTesting
static final String PARALLEL_REQUEST_URL_KEY =
"android.support.customtabs.PARALLEL_REQUEST_URL";
private static final CustomTabsConnection sInstance = private static final CustomTabsConnection sInstance =
AppHooks.get().createCustomTabsConnection(); AppHooks.get().createCustomTabsConnection();
...@@ -894,6 +900,12 @@ public class CustomTabsConnection { ...@@ -894,6 +900,12 @@ public class CustomTabsConnection {
// processing from now on. // processing from now on.
if (mWarmupTasks != null) mWarmupTasks.cancel(); if (mWarmupTasks != null) mWarmupTasks.cancel();
maybePreconnectToRedirectEndpoint(session, url, intent);
maybeStartParallelRequest(session, intent);
}
private void maybePreconnectToRedirectEndpoint(
CustomTabsSessionToken session, String url, Intent intent) {
// For the preconnection to not be a no-op, we need more than just the native library. // For the preconnection to not be a no-op, we need more than just the native library.
if (!ChromeBrowserInitializer.getInstance(mContext).hasNativeInitializationCompleted()) { if (!ChromeBrowserInitializer.getInstance(mContext).hasNativeInitializationCompleted()) {
return; return;
...@@ -914,19 +926,29 @@ public class CustomTabsConnection { ...@@ -914,19 +926,29 @@ public class CustomTabsConnection {
Profile.getLastUsedProfile(), redirectEndpoint.toString()); Profile.getLastUsedProfile(), redirectEndpoint.toString());
} }
/** @return Whether {@code session} can create a parallel request for a given {@code origin}. */ private void maybeStartParallelRequest(CustomTabsSessionToken session, Intent intent) {
if (!mClientManager.getAllowParallelRequestForSession(session)) return;
Uri referrer = intent.getParcelableExtra(PARALLEL_REQUEST_REFERRER_KEY);
Uri url = intent.getParcelableExtra(PARALLEL_REQUEST_URL_KEY);
if (referrer == null || url == null) return;
startParallelRequest(session, url, referrer);
}
/** @return Whether {@code session} can create a parallel request for a given
* {@code referrer}.
*/
@VisibleForTesting @VisibleForTesting
boolean canDoParallelRequest(CustomTabsSessionToken session, String origin) { boolean canDoParallelRequest(CustomTabsSessionToken session, Uri referrer) {
ThreadUtils.assertOnUiThread(); ThreadUtils.assertOnUiThread();
// The restrictions are: // The restrictions are:
// - Native initialization: Required to get the profile, and the feature state. // - Native initialization: Required to get the profile, and the feature state.
// - Feature check // - Feature check
// - The origin is allowed. // - The referrer's origin is allowed.
// //
// TODO(lizeb): Relax the restrictions. // TODO(lizeb): Relax the restrictions.
return ChromeBrowserInitializer.getInstance(mContext).hasNativeInitializationCompleted() return ChromeBrowserInitializer.getInstance(mContext).hasNativeInitializationCompleted()
&& ChromeFeatureList.isEnabled(ChromeFeatureList.CCT_PARALLEL_REQUEST) && ChromeFeatureList.isEnabled(ChromeFeatureList.CCT_PARALLEL_REQUEST)
&& mClientManager.isFirstPartyOriginForSession(session, Uri.parse(origin)); && mClientManager.isFirstPartyOriginForSession(session, referrer);
} }
/** /**
...@@ -934,20 +956,21 @@ public class CustomTabsConnection { ...@@ -934,20 +956,21 @@ public class CustomTabsConnection {
* *
* @param session Calling context session. * @param session Calling context session.
* @param url URL to send the request to. * @param url URL to send the request to.
* @param origin Origin to use. * @param referrer Referrer (and first party for cookies) to use.
* @return Whether the request started. False if the session is not authorized to use the * @return Whether the request started. False if the session is not authorized to use the
* provided origin, if Chrome hasn't been initialized, or the feature is disabled. * provided origin, if Chrome hasn't been initialized, or the feature is disabled.
* Also fails if the URL is neither HTTPS not HTTP. * Also fails if the URL is neither HTTPS not HTTP.
*/ */
@VisibleForTesting @VisibleForTesting
boolean startParallelRequest(CustomTabsSessionToken session, String url, String origin) { boolean startParallelRequest(CustomTabsSessionToken session, Uri url, Uri referrer) {
ThreadUtils.assertOnUiThread(); ThreadUtils.assertOnUiThread();
if (TextUtils.isEmpty(url) || !isValid(Uri.parse(url)) if (url.toString().equals("") || !isValid(url)
|| !canDoParallelRequest(session, origin)) { || !canDoParallelRequest(session, referrer)) {
return false; return false;
} }
nativeCreateAndStartDetachedResourceRequest(Profile.getLastUsedProfile(), url, origin); nativeCreateAndStartDetachedResourceRequest(
Profile.getLastUsedProfile(), url.toString(), referrer.toString());
return true; return true;
} }
......
...@@ -58,6 +58,7 @@ import org.junit.Assert; ...@@ -58,6 +58,7 @@ import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.chromium.base.ActivityState; import org.chromium.base.ActivityState;
...@@ -109,6 +110,8 @@ import org.chromium.chrome.browser.util.ColorUtils; ...@@ -109,6 +110,8 @@ import org.chromium.chrome.browser.util.ColorUtils;
import org.chromium.chrome.test.ChromeActivityTestRule; import org.chromium.chrome.test.ChromeActivityTestRule;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner; import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.util.ChromeTabUtils; import org.chromium.chrome.test.util.ChromeTabUtils;
import org.chromium.chrome.test.util.browser.Features;
import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
import org.chromium.chrome.test.util.browser.LocationSettingsTestUtil; import org.chromium.chrome.test.util.browser.LocationSettingsTestUtil;
import org.chromium.chrome.test.util.browser.contextmenu.ContextMenuUtils; import org.chromium.chrome.test.util.browser.contextmenu.ContextMenuUtils;
import org.chromium.content.browser.test.util.Criteria; import org.chromium.content.browser.test.util.Criteria;
...@@ -198,6 +201,8 @@ public class CustomTabActivityTest { ...@@ -198,6 +201,8 @@ public class CustomTabActivityTest {
@Rule @Rule
public final ScreenShooter mScreenShooter = new ScreenShooter(); public final ScreenShooter mScreenShooter = new ScreenShooter();
@Rule
public TestRule mProcessor = new Features.InstrumentationProcessor();
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
...@@ -2046,6 +2051,39 @@ public class CustomTabActivityTest { ...@@ -2046,6 +2051,39 @@ public class CustomTabActivityTest {
startHiddenTabAndChangeFragment(false, false); startHiddenTabAndChangeFragment(false, false);
} }
@Test
@SmallTest
@EnableFeatures(ChromeFeatureList.CCT_PARALLEL_REQUEST)
public void testParallelRequest() throws Exception {
String url = mTestServer.getURL("/echoheader?Cookie");
Uri requestUri = Uri.parse(mTestServer.getURL("/set-cookie?acookie"));
final Context context = InstrumentationRegistry.getTargetContext();
Intent intent = CustomTabsTestUtils.createMinimalCustomTabIntent(context, url);
final CustomTabsSessionToken token =
CustomTabsSessionToken.getSessionTokenFromIntent(intent);
// warmup(), create session, allow parallel requests, allow origin.
CustomTabsConnection connection = CustomTabsTestUtils.warmUpAndWait();
final Origin origin = new Origin(requestUri);
Assert.assertTrue(connection.newSession(token));
connection.mClientManager.setAllowParallelRequestForSession(token, true);
ThreadUtils.runOnUiThreadBlocking(() -> {
OriginVerifier.addVerifiedOriginForPackage(
context.getPackageName(), origin, CustomTabsService.RELATION_USE_AS_ORIGIN);
});
intent.putExtra(CustomTabsConnection.PARALLEL_REQUEST_URL_KEY, requestUri);
intent.putExtra(
CustomTabsConnection.PARALLEL_REQUEST_REFERRER_KEY, Uri.parse(origin.toString()));
mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);
Tab tab = mCustomTabActivityTestRule.getActivity().getActivityTab();
String content = JavaScriptUtils.executeJavaScriptAndWaitForResult(
tab.getWebContents(), "document.body.textContent");
Assert.assertEquals("\"acookie\"", content);
}
/** /**
* Tests the following scenario: * Tests the following scenario:
* - warmup() + mayLaunchUrl("http://example.com/page.html#first-fragment") * - warmup() + mayLaunchUrl("http://example.com/page.html#first-fragment")
......
...@@ -6,6 +6,7 @@ package org.chromium.chrome.browser.customtabs; ...@@ -6,6 +6,7 @@ package org.chromium.chrome.browser.customtabs;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri;
import android.support.customtabs.CustomTabsService; import android.support.customtabs.CustomTabsService;
import android.support.customtabs.CustomTabsSessionToken; import android.support.customtabs.CustomTabsSessionToken;
import android.support.test.InstrumentationRegistry; import android.support.test.InstrumentationRegistry;
...@@ -34,7 +35,6 @@ import org.chromium.chrome.test.ChromeJUnit4ClassRunner; ...@@ -34,7 +35,6 @@ import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.util.browser.Features; import org.chromium.chrome.test.util.browser.Features;
import org.chromium.chrome.test.util.browser.Features.EnableFeatures; import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
import org.chromium.content.browser.test.util.JavaScriptUtils; import org.chromium.content.browser.test.util.JavaScriptUtils;
import org.chromium.net.GURLUtils;
import org.chromium.net.test.EmbeddedTestServer; import org.chromium.net.test.EmbeddedTestServer;
/** Tests for detached resource requests. */ /** Tests for detached resource requests. */
...@@ -50,7 +50,7 @@ public class DetachedResourceRequestTest { ...@@ -50,7 +50,7 @@ public class DetachedResourceRequestTest {
private EmbeddedTestServer mServer; private EmbeddedTestServer mServer;
private static final String PRIVATE_DATA_DIRECTORY_SUFFIX = "chrome"; private static final String PRIVATE_DATA_DIRECTORY_SUFFIX = "chrome";
private static final String ORIGIN = "http://cats.google.com"; private static final Uri ORIGIN = Uri.parse("http://cats.google.com");
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
...@@ -83,8 +83,8 @@ public class DetachedResourceRequestTest { ...@@ -83,8 +83,8 @@ public class DetachedResourceRequestTest {
() -> { Assert.assertFalse(mConnection.canDoParallelRequest(session, ORIGIN)); }); () -> { Assert.assertFalse(mConnection.canDoParallelRequest(session, ORIGIN)); });
ThreadUtils.runOnUiThreadBlocking(() -> { ThreadUtils.runOnUiThreadBlocking(() -> {
String packageName = mContext.getPackageName(); String packageName = mContext.getPackageName();
OriginVerifier.addVerifiedOriginForPackage( OriginVerifier.addVerifiedOriginForPackage(packageName, new Origin(ORIGIN.toString()),
packageName, new Origin(ORIGIN), CustomTabsService.RELATION_USE_AS_ORIGIN); CustomTabsService.RELATION_USE_AS_ORIGIN);
Assert.assertTrue(mConnection.canDoParallelRequest(session, ORIGIN)); Assert.assertTrue(mConnection.canDoParallelRequest(session, ORIGIN));
}); });
} }
...@@ -97,12 +97,14 @@ public class DetachedResourceRequestTest { ...@@ -97,12 +97,14 @@ public class DetachedResourceRequestTest {
ThreadUtils.runOnUiThreadBlocking(() -> { ThreadUtils.runOnUiThreadBlocking(() -> {
Assert.assertFalse("Should not allow android-app:// scheme", Assert.assertFalse("Should not allow android-app:// scheme",
mConnection.startParallelRequest( mConnection.startParallelRequest(
session, "android-app://this.is.an.android.app", ORIGIN)); session, Uri.parse("android-app://this.is.an.android.app"), ORIGIN));
Assert.assertFalse("Should not allow an empty URL", Assert.assertFalse("Should not allow an empty URL",
mConnection.startParallelRequest(session, "", ORIGIN)); mConnection.startParallelRequest(session, Uri.parse(""), ORIGIN));
Assert.assertFalse("Should not allow an arbitrary origin", Assert.assertFalse("Should not allow an arbitrary origin",
mConnection.startParallelRequest(session, "HTTPS://foo.bar", "wrong://origin")); mConnection.startParallelRequest(
Assert.assertTrue(mConnection.startParallelRequest(session, "HTTP://foo.bar", ORIGIN)); session, Uri.parse("HTTPS://foo.bar"), Uri.parse("wrong://origin")));
Assert.assertTrue(
mConnection.startParallelRequest(session, Uri.parse("HTTP://foo.bar"), ORIGIN));
}); });
} }
...@@ -122,7 +124,7 @@ public class DetachedResourceRequestTest { ...@@ -122,7 +124,7 @@ public class DetachedResourceRequestTest {
}); });
mServer.start(); mServer.start();
String url = mServer.getURL("/echotitle"); Uri url = Uri.parse(mServer.getURL("/echotitle"));
ThreadUtils.runOnUiThread( ThreadUtils.runOnUiThread(
() -> Assert.assertTrue(mConnection.startParallelRequest(session, url, ORIGIN))); () -> Assert.assertTrue(mConnection.startParallelRequest(session, url, ORIGIN)));
cb.waitForCallback(0, 1); cb.waitForCallback(0, 1);
...@@ -134,7 +136,7 @@ public class DetachedResourceRequestTest { ...@@ -134,7 +136,7 @@ public class DetachedResourceRequestTest {
public void testCanSetCookie() throws Exception { public void testCanSetCookie() throws Exception {
CustomTabsSessionToken session = prepareSession(); CustomTabsSessionToken session = prepareSession();
mServer = EmbeddedTestServer.createAndStartServer(mContext); mServer = EmbeddedTestServer.createAndStartServer(mContext);
final String url = mServer.getURL("/set-cookie?acookie"); final Uri url = Uri.parse(mServer.getURL("/set-cookie?acookie"));
ThreadUtils.runOnUiThreadBlocking( ThreadUtils.runOnUiThreadBlocking(
() -> Assert.assertTrue(mConnection.startParallelRequest(session, url, ORIGIN))); () -> Assert.assertTrue(mConnection.startParallelRequest(session, url, ORIGIN)));
...@@ -159,7 +161,7 @@ public class DetachedResourceRequestTest { ...@@ -159,7 +161,7 @@ public class DetachedResourceRequestTest {
Assert.assertFalse(prefs.isBlockThirdPartyCookiesEnabled()); Assert.assertFalse(prefs.isBlockThirdPartyCookiesEnabled());
prefs.setBlockThirdPartyCookiesEnabled(true); prefs.setBlockThirdPartyCookiesEnabled(true);
}); });
final String url = mServer.getURL("/set-cookie?acookie"); final Uri url = Uri.parse(mServer.getURL("/set-cookie?acookie"));
ThreadUtils.runOnUiThreadBlocking( ThreadUtils.runOnUiThreadBlocking(
() -> Assert.assertTrue(mConnection.startParallelRequest(session, url, ORIGIN))); () -> Assert.assertTrue(mConnection.startParallelRequest(session, url, ORIGIN)));
...@@ -184,9 +186,9 @@ public class DetachedResourceRequestTest { ...@@ -184,9 +186,9 @@ public class DetachedResourceRequestTest {
Assert.assertFalse(prefs.isBlockThirdPartyCookiesEnabled()); Assert.assertFalse(prefs.isBlockThirdPartyCookiesEnabled());
prefs.setBlockThirdPartyCookiesEnabled(true); prefs.setBlockThirdPartyCookiesEnabled(true);
}); });
final String url = mServer.getURL("/set-cookie?acookie"); final Uri url = Uri.parse(mServer.getURL("/set-cookie?acookie"));
String origin = GURLUtils.getOrigin(url); final Uri origin = Uri.parse(new Origin(url).toString());
CustomTabsSessionToken session = prepareSession(origin); CustomTabsSessionToken session = prepareSession(url);
ThreadUtils.runOnUiThreadBlocking( ThreadUtils.runOnUiThreadBlocking(
() -> Assert.assertTrue(mConnection.startParallelRequest(session, url, origin))); () -> Assert.assertTrue(mConnection.startParallelRequest(session, url, origin)));
...@@ -205,14 +207,14 @@ public class DetachedResourceRequestTest { ...@@ -205,14 +207,14 @@ public class DetachedResourceRequestTest {
return prepareSession(ORIGIN); return prepareSession(ORIGIN);
} }
private CustomTabsSessionToken prepareSession(String origin) throws Exception { private CustomTabsSessionToken prepareSession(Uri origin) throws Exception {
final CustomTabsSessionToken session = final CustomTabsSessionToken session =
CustomTabsSessionToken.createMockSessionTokenForTesting(); CustomTabsSessionToken.createMockSessionTokenForTesting();
Assert.assertTrue(mConnection.newSession(session)); Assert.assertTrue(mConnection.newSession(session));
CustomTabsTestUtils.warmUpAndWait(); CustomTabsTestUtils.warmUpAndWait();
ThreadUtils.runOnUiThreadBlocking(() -> { ThreadUtils.runOnUiThreadBlocking(() -> {
OriginVerifier.addVerifiedOriginForPackage(mContext.getPackageName(), OriginVerifier.addVerifiedOriginForPackage(mContext.getPackageName(),
new Origin(origin), CustomTabsService.RELATION_USE_AS_ORIGIN); new Origin(origin.toString()), CustomTabsService.RELATION_USE_AS_ORIGIN);
Assert.assertTrue(mConnection.canDoParallelRequest(session, origin)); Assert.assertTrue(mConnection.canDoParallelRequest(session, origin));
}); });
return session; return session;
......
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