blob: f8e36c76c779f67a4f08bc4ecf085d7fc2c484ea [file] [log] [blame]
// Copyright 2018 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.customtabs.dynamicmodule;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.customtabs.CustomTabsService;
import android.support.customtabs.PostMessageBackend;
import android.support.test.filters.SmallTest;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.chromium.base.library_loader.LibraryLoader;
import org.chromium.base.library_loader.LibraryProcessType;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.chrome.browser.AppHooksModule;
import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.customtabs.CustomTabActivity;
import org.chromium.chrome.browser.customtabs.CustomTabActivityTestRule;
import org.chromium.chrome.browser.customtabs.dynamicmodule.CustomTabsDynamicModuleTestUtils.AppHooksModuleForTest;
import org.chromium.chrome.browser.customtabs.dynamicmodule.CustomTabsDynamicModuleTestUtils.IntentBuilder;
import org.chromium.chrome.browser.dependency_injection.ModuleOverridesRule;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.util.ChromeTabUtils;
import org.chromium.chrome.test.util.browser.Features;
import org.chromium.net.test.util.TestWebServer;
/**
* Instrumentation tests for the CCT Dynamic Module post message API.
*/
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public class CustomTabsDynamicModulePostMessageTest {
private final TestRule mModuleOverridesRule = new ModuleOverridesRule()
.setOverride(AppHooksModule.Factory.class, AppHooksModuleForTest::new);
private final CustomTabActivityTestRule mActivityRule = new CustomTabActivityTestRule();
@Rule
public final TestRule mOverrideModulesThenLaunchRule =
RuleChain.outerRule(mModuleOverridesRule).around(mActivityRule);
private TestWebServer mServer;
private static final String JS_MESSAGE = "from_js";
private static final String TITLE_FROM_POSTMESSAGE_TO_CHANNEL =
"<!DOCTYPE html><html><body>"
+ " <script>"
+ " var received = '';"
+ " onmessage = function (e) {"
+ " var myport = e.ports[0];"
+ " myport.onmessage = function (f) {"
+ " received += f.data;"
+ " document.title = received;"
+ " }"
+ " }"
+ " </script>"
+ "</body></html>";
private static final String MESSAGE_FROM_PAGE_TO_CHANNEL =
"<!DOCTYPE html><html><body>"
+ " <script>"
+ " onmessage = function (e) {"
+ " if (e.ports != null && e.ports.length > 0) {"
+ " e.ports[0].postMessage(\"" + JS_MESSAGE + "\");"
+ " }"
+ " }"
+ " </script>"
+ "</body></html>";
private static final Uri FAKE_ORIGIN_URI = Uri.parse("android-app://com.google.test");
@Before
public void setUp() throws Exception {
LibraryLoader.getInstance().ensureInitialized(LibraryProcessType.PROCESS_BROWSER);
mServer = TestWebServer.start();
}
@After
public void tearDown() throws Exception {
mServer.shutdown();
}
/**
* Tests the sent postMessage requests not only return success, but is also received by page.
*/
@Test
@SmallTest
@Features.EnableFeatures(ChromeFeatureList.CCT_MODULE_POST_MESSAGE)
public void testPostMessageFromDynamicModuleReceivedInPage() throws Exception {
final String url =
mServer.setResponse("/test.html", TITLE_FROM_POSTMESSAGE_TO_CHANNEL, null);
Intent intent = new IntentBuilder(url).build();
mActivityRule.startCustomTabActivityWithIntent(intent);
ChromeTabUtils.waitForTabPageLoaded(getActivity().getActivityTab(), url);
DynamicModuleCoordinator coordinator = getModuleCoordinator();
coordinator.maybeInitialiseDynamicModulePostMessageHandler(new PostMessageBackend() {
@Override
public boolean onPostMessage(String message, Bundle extras) {
return true;
}
@Override
public boolean onNotifyMessageChannelReady(Bundle extras) {
// Now attempt to post a message.
assertEquals(coordinator.postMessage("New title"),
CustomTabsService.RESULT_SUCCESS);
return true;
}
@Override
public void onDisconnectChannel(Context appContext) {}
});
assertTrue(coordinator.requestPostMessageChannel(FAKE_ORIGIN_URI));
// The callback registered above will post a message once the requested channel is ready.
ChromeTabUtils.waitForTitle(getActivity().getActivityTab(), "New title");
}
/**
* Tests the postMessage requests sent from the page is received on the client side.
*/
@Test
@SmallTest
@Features.EnableFeatures(
{ChromeFeatureList.CCT_MODULE, ChromeFeatureList.CCT_MODULE_POST_MESSAGE})
public void testPostMessageReceivedFromPageByDynamicModule() throws Exception {
final CallbackHelper messageChannelHelper = new CallbackHelper();
final CallbackHelper onPostMessageHelper = new CallbackHelper();
final String url = mServer.setResponse("/test.html", MESSAGE_FROM_PAGE_TO_CHANNEL, null);
Intent intent = new IntentBuilder(url).build();
mActivityRule.startCustomTabActivityWithIntent(intent);
ChromeTabUtils.waitForTabPageLoaded(getActivity().getActivityTab(), url);
DynamicModuleCoordinator coordinator = getModuleCoordinator();
coordinator.maybeInitialiseDynamicModulePostMessageHandler(new PostMessageBackend() {
@Override
public boolean onPostMessage(String message, Bundle extras) {
onPostMessageHelper.notifyCalled();
return true;
}
@Override
public boolean onNotifyMessageChannelReady(Bundle extras) {
messageChannelHelper.notifyCalled();
return true;
}
@Override
public void onDisconnectChannel(Context appContext) {}
});
assertTrue(coordinator.requestPostMessageChannel(FAKE_ORIGIN_URI));
messageChannelHelper.waitForCallback();
onPostMessageHelper.waitForCallback();
}
/**
* Tests the postMessage requests sent from the page is received on the client side.
*/
@Test
@SmallTest
@Features.EnableFeatures(ChromeFeatureList.CCT_MODULE_POST_MESSAGE)
@Features.DisableFeatures(ChromeFeatureList.CCT_MODULE)
public void testPostMessageFromDynamicModuleDisallowedBeforeModuleLoaded() throws Exception {
final CallbackHelper messageChannelHelper = new CallbackHelper();
final CallbackHelper onPostMessageHelper = new CallbackHelper();
final String url = mServer.setResponse("/test.html", MESSAGE_FROM_PAGE_TO_CHANNEL, null);
Intent intent = new IntentBuilder(url).build();
mActivityRule.startCustomTabActivityWithIntent(intent);
ChromeTabUtils.waitForTabPageLoaded(getActivity().getActivityTab(), url);
// Dynamic module is not instantiated because CCT_MODULE feature is disabled,
// resolve it manually. Module loading is started in onFinishNativeInitialization()
// onFinishNativeInitialization() call happens before therefore if we instantiate
// DynamicModuleCoordinator now, the module will not be loaded.
DynamicModuleCoordinator coordinator = getModuleCoordinator();
assertFalse(coordinator.isModuleLoading() || coordinator.isModuleLoaded()
|| coordinator.hasModuleFailedToLoad());
// We shouldn't be able to open a channel or post messages yet.
assertFalse(coordinator.requestPostMessageChannel(FAKE_ORIGIN_URI));
assertEquals(coordinator.postMessage("Message"),
CustomTabsService.RESULT_FAILURE_DISALLOWED);
// Now fake initialisation of the dynamic module.
coordinator.maybeInitialiseDynamicModulePostMessageHandler(
new PostMessageBackend() {
@Override
public boolean onPostMessage(String message, Bundle extras) {
onPostMessageHelper.notifyCalled();
return true;
}
@Override
public boolean onNotifyMessageChannelReady(Bundle extras) {
messageChannelHelper.notifyCalled();
return true;
}
@Override
public void onDisconnectChannel(Context appContext) {}
});
// We can now request a postMessage channel.
assertTrue(coordinator.requestPostMessageChannel(FAKE_ORIGIN_URI));
}
@Test
@SmallTest
@Features.EnableFeatures(ChromeFeatureList.CCT_MODULE)
@Features.DisableFeatures(ChromeFeatureList.CCT_MODULE_POST_MESSAGE)
public void testPostMessageFromDynamicModuleDisallowedWhenFeatureDisabled() throws Exception {
final String url = mServer.setResponse("/test.html", MESSAGE_FROM_PAGE_TO_CHANNEL, null);
Intent intent = new IntentBuilder(url).build();
mActivityRule.startCustomTabActivityWithIntent(intent);
ChromeTabUtils.waitForTabPageLoaded(getActivity().getActivityTab(), url);
DynamicModuleCoordinator coordinator = getModuleCoordinator();
// We shouldn't be able to open a channel or post messages yet.
assertFalse(coordinator.requestPostMessageChannel(FAKE_ORIGIN_URI));
assertEquals(coordinator.postMessage("Message"),
CustomTabsService.RESULT_FAILURE_DISALLOWED);
}
private CustomTabActivity getActivity() {
return mActivityRule.getActivity();
}
private DynamicModuleCoordinator getModuleCoordinator() {
return getActivity().getComponent().resolveDynamicModuleCoordinator();
}
}