Commit 8405148f authored by barkerd's avatar barkerd Committed by Commit Bot

Expose a postMessage API to dynamic modules

This uses a PostMessageHandler internally to communicate with the web contents.
The PostMessageHandler is initialised once the dynamic module has been loaded
and is reset each time the web contents change.

A new class, ActivityDelegatePostMessageBackend, replaces the service backend
usually used by PostMessageHandler and forwards all events along to the
dynamic module if it's present.

Bug: 898840
Change-Id: Ia308c84cd771d2fdb96fc076472488fc48946162
Reviewed-on: https://chromium-review.googlesource.com/c/1323669
Commit-Queue: David Barker <barkerd@google.com>
Reviewed-by: default avatarMichael van Ouwerkerk <mvanouwerkerk@chromium.org>
Reviewed-by: default avatarPeter Conn <peconn@chromium.org>
Cr-Commit-Position: refs/heads/master@{#607587}
parent 37182764
...@@ -163,6 +163,7 @@ public abstract class ChromeFeatureList { ...@@ -163,6 +163,7 @@ public abstract class ChromeFeatureList {
public static final String CCT_BACKGROUND_TAB = "CCTBackgroundTab"; public static final String CCT_BACKGROUND_TAB = "CCTBackgroundTab";
public static final String CCT_MODULE = "CCTModule"; public static final String CCT_MODULE = "CCTModule";
public static final String CCT_MODULE_CACHE = "CCTModuleCache"; public static final String CCT_MODULE_CACHE = "CCTModuleCache";
public static final String CCT_MODULE_POST_MESSAGE = "CCTModulePostMessage";
public static final String CCT_EXTERNAL_LINK_HANDLING = "CCTExternalLinkHandling"; public static final String CCT_EXTERNAL_LINK_HANDLING = "CCTExternalLinkHandling";
public static final String CCT_PARALLEL_REQUEST = "CCTParallelRequest"; public static final String CCT_PARALLEL_REQUEST = "CCTParallelRequest";
public static final String CCT_POST_MESSAGE_API = "CCTPostMessageAPI"; public static final String CCT_POST_MESSAGE_API = "CCTPostMessageAPI";
......
...@@ -24,7 +24,9 @@ import android.support.annotation.CallSuper; ...@@ -24,7 +24,9 @@ import android.support.annotation.CallSuper;
import android.support.annotation.IntDef; import android.support.annotation.IntDef;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.customtabs.CustomTabsIntent; import android.support.customtabs.CustomTabsIntent;
import android.support.customtabs.CustomTabsService;
import android.support.customtabs.CustomTabsSessionToken; import android.support.customtabs.CustomTabsSessionToken;
import android.support.customtabs.PostMessageBackend;
import android.support.v4.app.ActivityOptionsCompat; import android.support.v4.app.ActivityOptionsCompat;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Pair; import android.util.Pair;
...@@ -64,6 +66,7 @@ import org.chromium.chrome.browser.appmenu.AppMenuPropertiesDelegate; ...@@ -64,6 +66,7 @@ import org.chromium.chrome.browser.appmenu.AppMenuPropertiesDelegate;
import org.chromium.chrome.browser.autofill_assistant.AutofillAssistantUiController; import org.chromium.chrome.browser.autofill_assistant.AutofillAssistantUiController;
import org.chromium.chrome.browser.browserservices.BrowserSessionContentHandler; import org.chromium.chrome.browser.browserservices.BrowserSessionContentHandler;
import org.chromium.chrome.browser.browserservices.BrowserSessionContentUtils; import org.chromium.chrome.browser.browserservices.BrowserSessionContentUtils;
import org.chromium.chrome.browser.browserservices.PostMessageHandler;
import org.chromium.chrome.browser.compositor.layouts.LayoutManager; import org.chromium.chrome.browser.compositor.layouts.LayoutManager;
import org.chromium.chrome.browser.contextual_suggestions.ContextualSuggestionsModule; import org.chromium.chrome.browser.contextual_suggestions.ContextualSuggestionsModule;
import org.chromium.chrome.browser.customtabs.dependency_injection.CustomTabActivityComponent; import org.chromium.chrome.browser.customtabs.dependency_injection.CustomTabActivityComponent;
...@@ -192,6 +195,8 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent ...@@ -192,6 +195,8 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent
private ModuleEntryPoint mModuleEntryPoint; private ModuleEntryPoint mModuleEntryPoint;
@Nullable @Nullable
private ActivityDelegate mModuleActivityDelegate; private ActivityDelegate mModuleActivityDelegate;
@Nullable
private PostMessageHandler mDynamicModulePostMessageHandler;
private boolean mModuleOnStartPending; private boolean mModuleOnStartPending;
private boolean mModuleOnResumePending; private boolean mModuleOnResumePending;
...@@ -359,9 +364,22 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent ...@@ -359,9 +364,22 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent
mConnection.setActivityDelegateForSession(mSession, mModuleActivityDelegate, mConnection.setActivityDelegateForSession(mSession, mModuleActivityDelegate,
mModuleEntryPoint.getModuleVersion()); mModuleEntryPoint.getModuleVersion());
mModuleCallback = null; mModuleCallback = null;
// Initialise the PostMessageHandler for the current web contents.
maybeInitialiseDynamicModulePostMessageHandler(
new ActivityDelegatePostMessageBackend());
} }
} }
@VisibleForTesting
void maybeInitialiseDynamicModulePostMessageHandler(PostMessageBackend backend) {
// Only initialise the handler if the feature is enabled.
if (!ChromeFeatureList.isEnabled(ChromeFeatureList.CCT_MODULE_POST_MESSAGE)) return;
mDynamicModulePostMessageHandler = new PostMessageHandler(backend);
mDynamicModulePostMessageHandler.reset(getCurrentWebContents());
}
private void startModule() { private void startModule() {
assert mModuleActivityDelegate != null; assert mModuleActivityDelegate != null;
...@@ -410,6 +428,42 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent ...@@ -410,6 +428,42 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent
isModuleManagedUrl(mIntentDataProvider.getUrlToLoad())); isModuleManagedUrl(mIntentDataProvider.getUrlToLoad()));
} }
/**
* Requests a postMessage channel for a loaded dynamic module.
*
* <p>The initialisation work is posted to the UI thread because this method will be called by
* the dynamic module so we can't be sure of the thread it will be called on.
*
* @param postMessageOrigin The origin to use for messages posted to this channel.
* @return Whether it was possible to request a channel. Will return false if the dynamic module
* has not been loaded.
*/
public boolean requestPostMessageChannel(Uri postMessageOrigin) {
if (mDynamicModulePostMessageHandler == null) return false;
ThreadUtils.postOnUiThread(
() -> mDynamicModulePostMessageHandler.initializeWithPostMessageUri(postMessageOrigin));
return true;
}
/**
* Posts a message from a loaded dynamic module.
*
* @param message The message to post to the page. Nothing is assumed about the format of the
* message; we just post it as-is.
* @return Whether it was possible to post the message. Will always return {@link
* CustomTabsService#RESULT_FAILURE_DISALLOWED} if the dynamic module has not been
* loaded.
*/
public int postMessage(String message) {
// Use of the postMessage API is disallowed when the module has not been loaded.
if (mDynamicModulePostMessageHandler == null) {
return CustomTabsService.RESULT_FAILURE_DISALLOWED;
}
return mDynamicModulePostMessageHandler.postMessageFromClientApp(message);
}
@Override @Override
public boolean shouldAllocateChildConnection() { public boolean shouldAllocateChildConnection() {
return !mHasCreatedTabEarly && !mHasSpeculated return !mHasCreatedTabEarly && !mHasSpeculated
...@@ -730,7 +784,7 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent ...@@ -730,7 +784,7 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent
RecordHistogram.recordEnumeratedHistogram("CustomTabs.WebContentsStateOnLaunch", RecordHistogram.recordEnumeratedHistogram("CustomTabs.WebContentsStateOnLaunch",
webContentsStateOnLaunch, WebContentsState.NUM_ENTRIES); webContentsStateOnLaunch, WebContentsState.NUM_ENTRIES);
mConnection.resetPostMessageHandlerForSession(mSession, webContents); resetPostMessageHandlersForCurrentSession(webContents);
return webContents; return webContents;
} }
...@@ -743,6 +797,14 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent ...@@ -743,6 +797,14 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent
return asyncParams.getWebContents(); return asyncParams.getWebContents();
} }
private void resetPostMessageHandlersForCurrentSession(WebContents newWebContents) {
mConnection.resetPostMessageHandlerForSession(mSession, newWebContents);
if (mDynamicModulePostMessageHandler != null) {
mDynamicModulePostMessageHandler.reset(newWebContents);
}
}
/** /**
* Initializes tab handlers and observers, e.g., for metrics. * Initializes tab handlers and observers, e.g., for metrics.
* *
...@@ -1371,7 +1433,7 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent ...@@ -1371,7 +1433,7 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent
mMainTab = null; mMainTab = null;
// mHasCreatedTabEarly == true => mMainTab != null in the rest of the code. // mHasCreatedTabEarly == true => mMainTab != null in the rest of the code.
mHasCreatedTabEarly = false; mHasCreatedTabEarly = false;
mConnection.resetPostMessageHandlerForSession(mSession, null); resetPostMessageHandlersForCurrentSession(null);
tab.detachAndStartReparenting(intent, startActivityOptions, finalizeCallback); tab.detachAndStartReparenting(intent, startActivityOptions, finalizeCallback);
} else { } else {
// Temporarily allowing disk access while fixing. TODO: http://crbug.com/581860 // Temporarily allowing disk access while fixing. TODO: http://crbug.com/581860
...@@ -1505,6 +1567,38 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent ...@@ -1505,6 +1567,38 @@ public class CustomTabActivity extends ChromeActivity<CustomTabActivityComponent
} }
} }
/**
* A {@link PostMessageBackend} which delegates incoming notifications to the {@link
* ActivityDelegate} from the dynamic module.
*
* <p>If the dynamic module is not loaded, we ignore incoming notifications and always return
* false.
*/
private class ActivityDelegatePostMessageBackend implements PostMessageBackend {
@Override
public boolean onPostMessage(String message, Bundle extras) {
if (mModuleActivityDelegate != null) {
mModuleActivityDelegate.onPostMessage(message);
return true;
}
return false;
}
@Override
public boolean onNotifyMessageChannelReady(Bundle extras) {
if (mModuleActivityDelegate != null) {
mModuleActivityDelegate.onMessageChannelReady();
return true;
}
return false;
}
@Override
public void onDisconnectChannel(Context appContext) {
// Nothing to do.
}
}
@Override @Override
protected CustomTabActivityComponent createComponent(ChromeActivityCommonsModule commonsModule, protected CustomTabActivityComponent createComponent(ChromeActivityCommonsModule commonsModule,
ContextualSuggestionsModule contextualSuggestionsModule) { ContextualSuggestionsModule contextualSuggestionsModule) {
......
...@@ -15,112 +15,89 @@ import android.os.RemoteException; ...@@ -15,112 +15,89 @@ import android.os.RemoteException;
public class ActivityDelegate { public class ActivityDelegate {
private final IActivityDelegate mActivityDelegate; private final IActivityDelegate mActivityDelegate;
public ActivityDelegate(IActivityDelegate activityDelegate) { private interface RemoteRunnable { void run() throws RemoteException; }
mActivityDelegate = activityDelegate;
}
public void onCreate(Bundle savedInstanceState) { private interface RemoteCallable<T> { T call() throws RemoteException; }
private void safeRun(RemoteRunnable runnable) {
try { try {
mActivityDelegate.onCreate(savedInstanceState); runnable.run();
} catch (RemoteException e) { } catch (RemoteException e) {
assert false; assert false;
} }
} }
public void onPostCreate(Bundle savedInstanceState) { private <T> T safeCall(RemoteCallable<T> callable, T defaultReturn) {
try { try {
mActivityDelegate.onPostCreate(savedInstanceState); return callable.call();
} catch (RemoteException e) { } catch (RemoteException e) {
assert false; assert false;
} }
return defaultReturn;
}
public ActivityDelegate(IActivityDelegate activityDelegate) {
mActivityDelegate = activityDelegate;
}
public void onCreate(Bundle savedInstanceState) {
safeRun(() -> mActivityDelegate.onCreate(savedInstanceState));
}
public void onPostCreate(Bundle savedInstanceState) {
safeRun(() -> mActivityDelegate.onPostCreate(savedInstanceState));
} }
public void onStart() { public void onStart() {
try { safeRun(mActivityDelegate::onStart);
mActivityDelegate.onStart();
} catch (RemoteException e) {
assert false;
}
} }
public void onStop() { public void onStop() {
try { safeRun(mActivityDelegate::onStop);
mActivityDelegate.onStop();
} catch (RemoteException e) {
assert false;
}
} }
public void onWindowFocusChanged(boolean hasFocus) { public void onWindowFocusChanged(boolean hasFocus) {
try { safeRun(() -> mActivityDelegate.onWindowFocusChanged(hasFocus));
mActivityDelegate.onWindowFocusChanged(hasFocus);
} catch (RemoteException e) {
assert false;
}
} }
public void onSaveInstanceState(Bundle outState) { public void onSaveInstanceState(Bundle outState) {
try { safeRun(() -> mActivityDelegate.onSaveInstanceState(outState));
mActivityDelegate.onSaveInstanceState(outState);
} catch (RemoteException e) {
assert false;
}
} }
public void onRestoreInstanceState(Bundle savedInstanceState) { public void onRestoreInstanceState(Bundle savedInstanceState) {
try { safeRun(() -> mActivityDelegate.onRestoreInstanceState(savedInstanceState));
mActivityDelegate.onRestoreInstanceState(savedInstanceState);
} catch (RemoteException e) {
assert false;
}
} }
public void onResume() { public void onResume() {
try { safeRun(mActivityDelegate::onResume);
mActivityDelegate.onResume();
} catch (RemoteException e) {
assert false;
}
} }
public void onPause() { public void onPause() {
try { safeRun(mActivityDelegate::onPause);
mActivityDelegate.onPause();
} catch (RemoteException e) {
assert false;
}
} }
public void onDestroy() { public void onDestroy() {
try { safeRun(mActivityDelegate::onDestroy);
mActivityDelegate.onDestroy();
} catch (RemoteException e) {
assert false;
}
} }
public boolean onBackPressed() { public boolean onBackPressed() {
try { return safeCall(mActivityDelegate::onBackPressed, false);
return mActivityDelegate.onBackPressed();
} catch (RemoteException e) {
assert false;
}
return false;
} }
public void onBackPressedAsync(Runnable notHandledRunnable) { public void onBackPressedAsync(Runnable notHandledRunnable) {
try { safeRun(() -> mActivityDelegate.onBackPressedAsync(ObjectWrapper.wrap(notHandledRunnable)));
mActivityDelegate.onBackPressedAsync(ObjectWrapper.wrap(notHandledRunnable));
} catch (RemoteException e) {
assert false;
}
} }
public void onNavigationEvent(int navigationEvent, Bundle extras) { public void onNavigationEvent(int navigationEvent, Bundle extras) {
try { safeRun(() -> mActivityDelegate.onNavigationEvent(navigationEvent, extras));
mActivityDelegate.onNavigationEvent(navigationEvent, extras); }
} catch (RemoteException e) {
assert false; public void onMessageChannelReady() {
} safeRun(mActivityDelegate::onMessageChannelReady);
}
public void onPostMessage(String message) {
safeRun(() -> mActivityDelegate.onPostMessage(message));
} }
} }
...@@ -48,4 +48,14 @@ public class ActivityHostImpl extends BaseActivityHost { ...@@ -48,4 +48,14 @@ public class ActivityHostImpl extends BaseActivityHost {
public void setTopBarView(IObjectWrapper topBarView) { public void setTopBarView(IObjectWrapper topBarView) {
mActivity.setTopBarContentView(ObjectWrapper.unwrap(topBarView, View.class)); mActivity.setTopBarContentView(ObjectWrapper.unwrap(topBarView, View.class));
} }
@Override
public boolean requestPostMessageChannel(Uri postMessageOrigin) {
return mActivity.requestPostMessageChannel(postMessageOrigin);
}
@Override
public int postMessage(String message) {
return mActivity.postMessage(message);
}
} }
...@@ -8,6 +8,7 @@ import static org.hamcrest.Matchers.empty; ...@@ -8,6 +8,7 @@ import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
import static org.junit.Assert.fail;
import static org.chromium.base.test.util.Restriction.RESTRICTION_TYPE_NON_LOW_END_DEVICE; import static org.chromium.base.test.util.Restriction.RESTRICTION_TYPE_NON_LOW_END_DEVICE;
import static org.chromium.chrome.browser.customtabs.CustomTabActivityTestRule.LONG_TIMEOUT_MS; import static org.chromium.chrome.browser.customtabs.CustomTabActivityTestRule.LONG_TIMEOUT_MS;
...@@ -37,6 +38,7 @@ import android.support.customtabs.CustomTabsIntent; ...@@ -37,6 +38,7 @@ import android.support.customtabs.CustomTabsIntent;
import android.support.customtabs.CustomTabsService; import android.support.customtabs.CustomTabsService;
import android.support.customtabs.CustomTabsSession; import android.support.customtabs.CustomTabsSession;
import android.support.customtabs.CustomTabsSessionToken; import android.support.customtabs.CustomTabsSessionToken;
import android.support.customtabs.PostMessageBackend;
import android.support.test.InstrumentationRegistry; import android.support.test.InstrumentationRegistry;
import android.support.test.filters.MediumTest; import android.support.test.filters.MediumTest;
import android.support.test.filters.SmallTest; import android.support.test.filters.SmallTest;
...@@ -108,8 +110,10 @@ import org.chromium.chrome.browser.util.ColorUtils; ...@@ -108,8 +110,10 @@ 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.DisableFeatures;
import org.chromium.chrome.test.util.browser.Features.EnableFeatures; 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.TabTitleObserver;
import org.chromium.chrome.test.util.browser.contextmenu.ContextMenuUtils; import org.chromium.chrome.test.util.browser.contextmenu.ContextMenuUtils;
import org.chromium.content_public.browser.LoadUrlParams; import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.WebContentsObserver; import org.chromium.content_public.browser.WebContentsObserver;
...@@ -141,6 +145,7 @@ public class CustomTabActivityTest { ...@@ -141,6 +145,7 @@ public class CustomTabActivityTest {
public CustomTabActivityTestRule mCustomTabActivityTestRule = new CustomTabActivityTestRule(); public CustomTabActivityTestRule mCustomTabActivityTestRule = new CustomTabActivityTestRule();
private static final int TIMEOUT_PAGE_LOAD_SECONDS = 10; private static final int TIMEOUT_PAGE_LOAD_SECONDS = 10;
public static final int TITLE_UPDATE_TIMEOUT_MS = 3000;
private static final int MAX_MENU_CUSTOM_ITEMS = 5; private static final int MAX_MENU_CUSTOM_ITEMS = 5;
private static final int NUM_CHROME_MENU_ITEMS = 5; private static final int NUM_CHROME_MENU_ITEMS = 5;
private static final String TEST_PAGE = "/chrome/test/data/android/google.html"; private static final String TEST_PAGE = "/chrome/test/data/android/google.html";
...@@ -190,6 +195,7 @@ public class CustomTabActivityTest { ...@@ -190,6 +195,7 @@ public class CustomTabActivityTest {
+ " }" + " }"
+ " </script>" + " </script>"
+ "</body></html>"; + "</body></html>";
private static final Uri FAKE_ORIGIN_URI = Uri.parse("android-app://com.google.test");
private static int sIdToIncrement = 1; private static int sIdToIncrement = 1;
...@@ -1627,13 +1633,7 @@ public class CustomTabActivityTest { ...@@ -1627,13 +1633,7 @@ public class CustomTabActivityTest {
}); });
Assert.assertTrue(connection.postMessage(token, "New title", null) Assert.assertTrue(connection.postMessage(token, "New title", null)
== CustomTabsService.RESULT_SUCCESS); == CustomTabsService.RESULT_SUCCESS);
CriteriaHelper.pollInstrumentationThread(new Criteria() { waitForTitle("New title");
@Override
public boolean isSatisfied() {
final Tab currentTab = mCustomTabActivityTestRule.getActivity().getActivityTab();
return "New title".equals(currentTab.getTitle());
}
});
} }
/** /**
...@@ -1842,13 +1842,148 @@ public class CustomTabActivityTest { ...@@ -1842,13 +1842,148 @@ public class CustomTabActivityTest {
titleString += newMessage; titleString += newMessage;
final String title = titleString; final String title = titleString;
CriteriaHelper.pollUiThread(new Criteria() { waitForTitle(title);
}
/**
* Tests the sent postMessage requests not only return success, but is also received by page.
*/
@Test
@SmallTest
@EnableFeatures(ChromeFeatureList.CCT_MODULE_POST_MESSAGE)
public void testPostMessageFromDynamicModuleReceivedInPage() throws Exception {
final String url =
mWebServer.setResponse("/test.html", TITLE_FROM_POSTMESSAGE_TO_CHANNEL, null);
Context context = InstrumentationRegistry.getTargetContext();
Intent intent = CustomTabsTestUtils.createMinimalCustomTabIntent(context, url);
mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);
ChromeTabUtils.waitForTabPageLoaded(getActivity().getActivityTab(), url);
getActivity().maybeInitialiseDynamicModulePostMessageHandler(new PostMessageBackend() {
@Override @Override
public boolean isSatisfied() { public boolean onPostMessage(String message, Bundle extras) {
final Tab currentTab = mCustomTabActivityTestRule.getActivity().getActivityTab(); return true;
return title.equals(currentTab.getTitle()); }
@Override
public boolean onNotifyMessageChannelReady(Bundle extras) {
// Now attempt to post a message.
Assert.assertTrue(
getActivity().postMessage("New title") == CustomTabsService.RESULT_SUCCESS);
return true;
}
@Override
public void onDisconnectChannel(Context appContext) {}
});
Assert.assertTrue(getActivity().requestPostMessageChannel(FAKE_ORIGIN_URI));
// The callback registered above will post a message once the requested channel is ready.
waitForTitle("New title");
}
/**
* Tests the postMessage requests sent from the page is received on the client side.
*/
@Test
@SmallTest
@EnableFeatures(ChromeFeatureList.CCT_MODULE_POST_MESSAGE)
public void testPostMessageReceivedFromPageByDynamicModule() throws Exception {
final CallbackHelper messageChannelHelper = new CallbackHelper();
final CallbackHelper onPostMessageHelper = new CallbackHelper();
final String url = mWebServer.setResponse("/test.html", MESSAGE_FROM_PAGE_TO_CHANNEL, null);
Context context = InstrumentationRegistry.getTargetContext();
Intent intent = CustomTabsTestUtils.createMinimalCustomTabIntent(context, url);
mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);
ChromeTabUtils.waitForTabPageLoaded(getActivity().getActivityTab(), url);
getActivity().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) {}
}); });
Assert.assertTrue(getActivity().requestPostMessageChannel(FAKE_ORIGIN_URI));
messageChannelHelper.waitForCallback();
onPostMessageHelper.waitForCallback();
}
/**
* Tests the postMessage requests sent from the page is received on the client side.
*/
@Test
@SmallTest
@EnableFeatures(ChromeFeatureList.CCT_MODULE_POST_MESSAGE)
public void testPostMessageFromDynamicModuleDisallowedBeforeModuleLoaded() throws Exception {
final CallbackHelper messageChannelHelper = new CallbackHelper();
final CallbackHelper onPostMessageHelper = new CallbackHelper();
final String url = mWebServer.setResponse("/test.html", MESSAGE_FROM_PAGE_TO_CHANNEL, null);
Context context = InstrumentationRegistry.getTargetContext();
Intent intent = CustomTabsTestUtils.createMinimalCustomTabIntent(context, url);
mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);
ChromeTabUtils.waitForTabPageLoaded(getActivity().getActivityTab(), url);
// We shouldn't be able to open a channel or post messages yet.
Assert.assertFalse(getActivity().requestPostMessageChannel(FAKE_ORIGIN_URI));
Assert.assertTrue(getActivity().postMessage("Message")
== CustomTabsService.RESULT_FAILURE_DISALLOWED);
// Now fake initialisation of the dynamic module.
getActivity().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.
Assert.assertTrue(getActivity().requestPostMessageChannel(FAKE_ORIGIN_URI));
}
@Test
@SmallTest
@DisableFeatures(ChromeFeatureList.CCT_MODULE_POST_MESSAGE)
public void testPostMessageFromDynamicModuleDisallowedWhenFeatureDisabled() throws Exception {
final String url = mWebServer.setResponse("/test.html", MESSAGE_FROM_PAGE_TO_CHANNEL, null);
Context context = InstrumentationRegistry.getTargetContext();
Intent intent = CustomTabsTestUtils.createMinimalCustomTabIntent(context, url);
mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);
ChromeTabUtils.waitForTabPageLoaded(getActivity().getActivityTab(), url);
// We shouldn't be able to open a channel or post messages yet.
Assert.assertFalse(getActivity().requestPostMessageChannel(FAKE_ORIGIN_URI));
Assert.assertTrue(getActivity().postMessage("Message")
== CustomTabsService.RESULT_FAILURE_DISALLOWED);
} }
/** /**
...@@ -2809,4 +2944,14 @@ public class CustomTabActivityTest { ...@@ -2809,4 +2944,14 @@ public class CustomTabActivityTest {
historyObserver.getQueryCallback().waitForCallback(0); historyObserver.getQueryCallback().waitForCallback(0);
return historyObserver.getHistoryQueryResults(); return historyObserver.getHistoryQueryResults();
} }
private void waitForTitle(String newTitle) throws InterruptedException {
Tab currentTab = mCustomTabActivityTestRule.getActivity().getActivityTab();
TabTitleObserver titleObserver = new TabTitleObserver(currentTab, newTitle);
try {
titleObserver.waitForTitleUpdate(TITLE_UPDATE_TIMEOUT_MS);
} catch (TimeoutException e) {
fail("Tab title didn't update in time");
}
}
} }
...@@ -4191,6 +4191,9 @@ const FeatureEntry kFeatureEntries[] = { ...@@ -4191,6 +4191,9 @@ const FeatureEntry kFeatureEntries[] = {
FEATURE_WITH_PARAMS_VALUE_TYPE(chrome::android::kCCTModuleCache, FEATURE_WITH_PARAMS_VALUE_TYPE(chrome::android::kCCTModuleCache,
kCCTModuleCacheVariations, kCCTModuleCacheVariations,
"CCTModule")}, "CCTModule")},
{"cct-module-post-message", flag_descriptions::kCCTModulePostMessageName,
flag_descriptions::kCCTModulePostMessageDescription, kOsAndroid,
FEATURE_VALUE_TYPE(chrome::android::kCCTModulePostMessage)},
#endif #endif
{"enable-css-fragment-identifiers", {"enable-css-fragment-identifiers",
......
...@@ -83,6 +83,7 @@ const base::Feature* kFeaturesExposedToJava[] = { ...@@ -83,6 +83,7 @@ const base::Feature* kFeaturesExposedToJava[] = {
&kCCTExternalLinkHandling, &kCCTExternalLinkHandling,
&kCCTModule, &kCCTModule,
&kCCTModuleCache, &kCCTModuleCache,
&kCCTModulePostMessage,
&kCCTParallelRequest, &kCCTParallelRequest,
&kCCTPostMessageAPI, &kCCTPostMessageAPI,
&kCCTRedirectPreconnect, &kCCTRedirectPreconnect,
...@@ -225,6 +226,9 @@ const base::Feature kCCTModule{"CCTModule", base::FEATURE_DISABLED_BY_DEFAULT}; ...@@ -225,6 +226,9 @@ const base::Feature kCCTModule{"CCTModule", base::FEATURE_DISABLED_BY_DEFAULT};
const base::Feature kCCTModuleCache{"CCTModuleCache", const base::Feature kCCTModuleCache{"CCTModuleCache",
base::FEATURE_DISABLED_BY_DEFAULT}; base::FEATURE_DISABLED_BY_DEFAULT};
const base::Feature kCCTModulePostMessage{"CCTModulePostMessage",
base::FEATURE_DISABLED_BY_DEFAULT};
const base::Feature kCCTParallelRequest{"CCTParallelRequest", const base::Feature kCCTParallelRequest{"CCTParallelRequest",
base::FEATURE_ENABLED_BY_DEFAULT}; base::FEATURE_ENABLED_BY_DEFAULT};
......
...@@ -24,6 +24,7 @@ extern const base::Feature kCCTBackgroundTab; ...@@ -24,6 +24,7 @@ extern const base::Feature kCCTBackgroundTab;
extern const base::Feature kCCTExternalLinkHandling; extern const base::Feature kCCTExternalLinkHandling;
extern const base::Feature kCCTModule; extern const base::Feature kCCTModule;
extern const base::Feature kCCTModuleCache; extern const base::Feature kCCTModuleCache;
extern const base::Feature kCCTModulePostMessage;
extern const base::Feature kCCTParallelRequest; extern const base::Feature kCCTParallelRequest;
extern const base::Feature kCCTPostMessageAPI; extern const base::Feature kCCTPostMessageAPI;
extern const base::Feature kCCTRedirectPreconnect; extern const base::Feature kCCTRedirectPreconnect;
......
...@@ -2305,6 +2305,12 @@ const char kCCTModuleCacheDescription[] = ...@@ -2305,6 +2305,12 @@ const char kCCTModuleCacheDescription[] =
"Enables a cache for dynamically loaded modules in Chrome Custom Tabs. " "Enables a cache for dynamically loaded modules in Chrome Custom Tabs. "
"Under mild memory pressure the cache may be retained for some time"; "Under mild memory pressure the cache may be retained for some time";
const char kCCTModulePostMessageName[] =
"Chrome Custom Tabs Module postMessage API";
const char kCCTModulePostMessageDescription[] =
"Enables the postMessage API exposed to dynamically loaded modules in "
"Chrome Custom Tabs.";
const char kChromeDuetName[] = "Chrome Duet"; const char kChromeDuetName[] = "Chrome Duet";
const char kChromeDuetDescription[] = const char kChromeDuetDescription[] =
"Enables Chrome Duet, split toolbar Chrome Home, on Android."; "Enables Chrome Duet, split toolbar Chrome Home, on Android.";
......
...@@ -1391,6 +1391,9 @@ extern const char kCCTModuleDescription[]; ...@@ -1391,6 +1391,9 @@ extern const char kCCTModuleDescription[];
extern const char kCCTModuleCacheName[]; extern const char kCCTModuleCacheName[];
extern const char kCCTModuleCacheDescription[]; extern const char kCCTModuleCacheDescription[];
extern const char kCCTModulePostMessageName[];
extern const char kCCTModulePostMessageDescription[];
extern const char kChromeDuetName[]; extern const char kChromeDuetName[];
extern const char kChromeDuetDescription[]; extern const char kChromeDuetDescription[];
......
...@@ -29584,6 +29584,7 @@ from previous Chrome versions. ...@@ -29584,6 +29584,7 @@ from previous Chrome versions.
label="FramebustingNeedsSameOriginOrUserGesture:disabled"/> label="FramebustingNeedsSameOriginOrUserGesture:disabled"/>
<int value="-1176493523" label="enable-md-extensions"/> <int value="-1176493523" label="enable-md-extensions"/>
<int value="-1174267639" label="ClientLoFi:disabled"/> <int value="-1174267639" label="ClientLoFi:disabled"/>
<int value="-1173361620" label="CCTModulePostMessage:disabled"/>
<int value="-1172572865" label="NTPShowGoogleGInOmnibox:enabled"/> <int value="-1172572865" label="NTPShowGoogleGInOmnibox:enabled"/>
<int value="-1172204005" label="enable-offline-auto-reload-visible-only"/> <int value="-1172204005" label="enable-offline-auto-reload-visible-only"/>
<int value="-1167992523" label="DesktopPWAsCustomTabUI:disabled"/> <int value="-1167992523" label="DesktopPWAsCustomTabUI:disabled"/>
...@@ -31049,6 +31050,7 @@ from previous Chrome versions. ...@@ -31049,6 +31050,7 @@ from previous Chrome versions.
<int value="1589341623" label="disable-easy-unlock"/> <int value="1589341623" label="disable-easy-unlock"/>
<int value="1590278995" <int value="1590278995"
label="AutofillSendExperimentIdsInPaymentsRPCs:enabled"/> label="AutofillSendExperimentIdsInPaymentsRPCs:enabled"/>
<int value="1590300329" label="CCTModulePostMessage:enabled"/>
<int value="1591653786" label="SpeculativePreconnect:enabled"/> <int value="1591653786" label="SpeculativePreconnect:enabled"/>
<int value="1593720927" label="GamepadVibration:disabled"/> <int value="1593720927" label="GamepadVibration:disabled"/>
<int value="1593917165" label="SimplifiedNTP:disabled"/> <int value="1593917165" label="SimplifiedNTP:disabled"/>
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