| // Copyright 2015 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; |
| |
| import static org.hamcrest.Matchers.empty; |
| import static org.hamcrest.Matchers.equalTo; |
| import static org.hamcrest.Matchers.is; |
| 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.chrome.browser.customtabs.CustomTabActivityTestRule.LONG_TIMEOUT_MS; |
| |
| import android.app.Activity; |
| import android.app.Instrumentation; |
| import android.app.Instrumentation.ActivityMonitor; |
| import android.app.PendingIntent; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.res.Resources; |
| import android.graphics.Bitmap; |
| import android.graphics.Canvas; |
| import android.graphics.Color; |
| import android.graphics.drawable.BitmapDrawable; |
| import android.graphics.drawable.ColorDrawable; |
| import android.graphics.drawable.Drawable; |
| import android.net.Uri; |
| import android.os.Build; |
| import android.os.Bundle; |
| import android.os.SystemClock; |
| import android.support.annotation.DrawableRes; |
| import android.support.customtabs.CustomTabsCallback; |
| import android.support.customtabs.CustomTabsIntent; |
| import android.support.customtabs.CustomTabsService; |
| import android.support.customtabs.CustomTabsSession; |
| import android.support.customtabs.CustomTabsSessionToken; |
| import android.support.customtabs.PostMessageBackend; |
| import android.support.test.InstrumentationRegistry; |
| import android.support.test.filters.MediumTest; |
| import android.support.test.filters.SmallTest; |
| import android.support.test.uiautomator.UiDevice; |
| import android.support.v7.content.res.AppCompatResources; |
| import android.text.TextUtils; |
| import android.view.Menu; |
| import android.view.MenuItem; |
| import android.view.SubMenu; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.widget.EditText; |
| import android.widget.ImageButton; |
| import android.widget.RemoteViews; |
| import android.widget.TextView; |
| |
| 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.ActivityState; |
| import org.chromium.base.ApplicationStatus; |
| import org.chromium.base.ApplicationStatus.ActivityStateListener; |
| import org.chromium.base.ObserverList.RewindableIterator; |
| import org.chromium.base.PathUtils; |
| import org.chromium.base.ThreadUtils; |
| import org.chromium.base.library_loader.LibraryLoader; |
| import org.chromium.base.library_loader.LibraryProcessType; |
| import org.chromium.base.metrics.RecordHistogram; |
| import org.chromium.base.test.util.CallbackHelper; |
| import org.chromium.base.test.util.CommandLineFlags; |
| import org.chromium.base.test.util.DisabledTest; |
| import org.chromium.base.test.util.Feature; |
| import org.chromium.base.test.util.Restriction; |
| import org.chromium.base.test.util.RetryOnFailure; |
| import org.chromium.chrome.R; |
| import org.chromium.chrome.browser.AppHooksModule; |
| import org.chromium.chrome.browser.ChromeActivity; |
| import org.chromium.chrome.browser.ChromeFeatureList; |
| import org.chromium.chrome.browser.ChromeSwitches; |
| import org.chromium.chrome.browser.ChromeTabbedActivity; |
| import org.chromium.chrome.browser.IntentHandler; |
| import org.chromium.chrome.browser.TabsOpenedFromExternalAppTest; |
| import org.chromium.chrome.browser.WarmupManager; |
| import org.chromium.chrome.browser.appmenu.AppMenuHandler; |
| import org.chromium.chrome.browser.browserservices.BrowserSessionContentUtils; |
| import org.chromium.chrome.browser.browserservices.Origin; |
| import org.chromium.chrome.browser.browserservices.OriginVerifier; |
| import org.chromium.chrome.browser.customtabs.dynamicmodule.DynamicModuleCoordinator; |
| import org.chromium.chrome.browser.dependency_injection.ModuleFactoryOverrides; |
| import org.chromium.chrome.browser.document.ChromeLauncherActivity; |
| import org.chromium.chrome.browser.firstrun.FirstRunStatus; |
| import org.chromium.chrome.browser.history.BrowsingHistoryBridge; |
| import org.chromium.chrome.browser.history.HistoryItem; |
| import org.chromium.chrome.browser.history.TestBrowsingHistoryObserver; |
| import org.chromium.chrome.browser.infobar.InfoBarContainer; |
| import org.chromium.chrome.browser.metrics.PageLoadMetrics; |
| import org.chromium.chrome.browser.preferences.PrefServiceBridge; |
| import org.chromium.chrome.browser.tab.EmptyTabObserver; |
| import org.chromium.chrome.browser.tab.Tab; |
| import org.chromium.chrome.browser.tab.Tab.TabHidingType; |
| import org.chromium.chrome.browser.tab.TabObserver; |
| import org.chromium.chrome.browser.tab.TabTestUtils; |
| import org.chromium.chrome.browser.tabmodel.EmptyTabModelObserver; |
| import org.chromium.chrome.browser.tabmodel.TabModel.TabLaunchType; |
| import org.chromium.chrome.browser.tabmodel.TabModelSelector; |
| import org.chromium.chrome.browser.test.ScreenShooter; |
| import org.chromium.chrome.browser.toolbar.top.CustomTabToolbar; |
| import org.chromium.chrome.browser.util.ColorUtils; |
| import org.chromium.chrome.test.ChromeActivityTestRule; |
| import org.chromium.chrome.test.ChromeJUnit4ClassRunner; |
| 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.LocationSettingsTestUtil; |
| import org.chromium.chrome.test.util.browser.TabTitleObserver; |
| import org.chromium.chrome.test.util.browser.contextmenu.ContextMenuUtils; |
| import org.chromium.content_public.browser.LoadUrlParams; |
| import org.chromium.content_public.browser.WebContentsObserver; |
| import org.chromium.content_public.browser.test.util.ClickUtils; |
| import org.chromium.content_public.browser.test.util.Criteria; |
| import org.chromium.content_public.browser.test.util.CriteriaHelper; |
| import org.chromium.content_public.browser.test.util.DOMUtils; |
| import org.chromium.content_public.browser.test.util.JavaScriptUtils; |
| import org.chromium.content_public.browser.test.util.WebContentsUtils; |
| import org.chromium.net.test.EmbeddedTestServer; |
| import org.chromium.net.test.ServerCertificate; |
| import org.chromium.net.test.util.TestWebServer; |
| import org.chromium.ui.base.PageTransition; |
| import org.chromium.ui.mojom.WindowOpenDisposition; |
| import org.chromium.ui.test.util.UiRestriction; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.Semaphore; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.TimeoutException; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| /** |
| * Instrumentation tests for app menu, context menu, and toolbar of a {@link CustomTabActivity}. |
| */ |
| @RunWith(ChromeJUnit4ClassRunner.class) |
| @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE}) |
| public class CustomTabActivityTest { |
| @Rule |
| public CustomTabActivityTestRule mCustomTabActivityTestRule = new CustomTabActivityTestRule(); |
| |
| 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 NUM_CHROME_MENU_ITEMS = 5; |
| private static final String TEST_PAGE = "/chrome/test/data/android/google.html"; |
| private static final String TEST_PAGE_2 = "/chrome/test/data/android/test.html"; |
| private static final String GEOLOCATION_PAGE = |
| "/chrome/test/data/geolocation/geolocation_on_load.html"; |
| private static final String SELECT_POPUP_PAGE = "/chrome/test/data/android/select.html"; |
| private static final String FRAGMENT_TEST_PAGE = "/chrome/test/data/android/fragment.html"; |
| private static final String TEST_MENU_TITLE = "testMenuTitle"; |
| private static final String PRIVATE_DATA_DIRECTORY_SUFFIX = "chrome"; |
| private static final String WEBLITE_PREFIX = "http://googleweblight.com/i?u="; |
| 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 String ONLOAD_TITLE_CHANGE = "<!DOCTYPE html><html><body>" |
| + " <script>" |
| + " window.onload = function () {" |
| + " document.title = \"nytimes.com\";" |
| + " }" |
| + " </script>" |
| + "</body></html>"; |
| private static final String DELAYED_TITLE_CHANGE = "<!DOCTYPE html><html><body>" |
| + " <script>" |
| + " window.onload = function () {" |
| + " setTimeout(function (){ document.title = \"nytimes.com\"}, 200);" |
| + " }" |
| + " </script>" |
| + "</body></html>"; |
| private static final Uri FAKE_ORIGIN_URI = Uri.parse("android-app://com.google.test"); |
| |
| private static int sIdToIncrement = 1; |
| |
| private String mTestPage; |
| private String mTestPage2; |
| private EmbeddedTestServer mTestServer; |
| private TestWebServer mWebServer; |
| |
| @Rule |
| public final ScreenShooter mScreenShooter = new ScreenShooter(); |
| |
| @Before |
| public void setUp() throws Exception { |
| ThreadUtils.runOnUiThreadBlocking(() -> FirstRunStatus.setFirstRunFlowComplete(true)); |
| |
| Context appContext = InstrumentationRegistry.getInstrumentation() |
| .getTargetContext() |
| .getApplicationContext(); |
| mTestServer = EmbeddedTestServer.createAndStartServer(appContext); |
| mTestPage = mTestServer.getURL(TEST_PAGE); |
| mTestPage2 = mTestServer.getURL(TEST_PAGE_2); |
| PathUtils.setPrivateDataDirectorySuffix(PRIVATE_DATA_DIRECTORY_SUFFIX); |
| LibraryLoader.getInstance().ensureInitialized(LibraryProcessType.PROCESS_BROWSER); |
| mWebServer = TestWebServer.start(); |
| |
| ModuleFactoryOverrides.setOverride(AppHooksModule.Factory.class, |
| CustomTabsDynamicModuleTestUtils.AppHooksModuleForTest::new); |
| } |
| |
| @After |
| public void tearDown() throws Exception { |
| ThreadUtils.runOnUiThreadBlocking(() -> FirstRunStatus.setFirstRunFlowComplete(false)); |
| |
| mTestServer.stopAndDestroyServer(); |
| |
| // finish() is called on a non-UI thread by the testing harness. Must hide the menu |
| // first, otherwise the UI is manipulated on a non-UI thread. |
| ThreadUtils.runOnUiThreadBlocking(() -> { |
| if (getActivity() == null) return; |
| AppMenuHandler handler = getActivity().getAppMenuHandler(); |
| if (handler != null) handler.hideAppMenu(); |
| }); |
| mWebServer.shutdown(); |
| |
| ModuleFactoryOverrides.clearOverrides(); |
| } |
| |
| private CustomTabActivity getActivity() { |
| return mCustomTabActivityTestRule.getActivity(); |
| } |
| |
| /** |
| * @see CustomTabsTestUtils#createMinimalCustomTabIntent(Context, String). |
| */ |
| private Intent createMinimalCustomTabIntent() { |
| return CustomTabsTestUtils.createMinimalCustomTabIntent( |
| InstrumentationRegistry.getTargetContext(), mTestPage); |
| } |
| |
| /** |
| * Add a bundle specifying a a number of custom menu entries. |
| * @param customTabIntent The intent to modify. |
| * @param numEntries The number of menu entries to add. |
| * @return The pending intent associated with the menu entries. |
| */ |
| private PendingIntent addMenuEntriesToIntent(Intent customTabIntent, int numEntries) { |
| return addMenuEntriesToIntent(customTabIntent, numEntries, new Intent()); |
| } |
| |
| /** |
| * Add a bundle specifying a custom menu entry. |
| * @param customTabIntent The intent to modify. |
| * @param numEntries The number of menu entries to add. |
| * @param callbackIntent The intent to use as the base for the pending intent. |
| * @return The pending intent associated with the menu entry. |
| */ |
| private PendingIntent addMenuEntriesToIntent( |
| Intent customTabIntent, int numEntries, Intent callbackIntent) { |
| PendingIntent pi = PendingIntent.getBroadcast(InstrumentationRegistry.getTargetContext(), 0, |
| callbackIntent, PendingIntent.FLAG_UPDATE_CURRENT); |
| ArrayList<Bundle> menuItems = new ArrayList<>(); |
| for (int i = 0; i < numEntries; i++) { |
| Bundle bundle = new Bundle(); |
| bundle.putString(CustomTabsIntent.KEY_MENU_ITEM_TITLE, TEST_MENU_TITLE); |
| bundle.putParcelable(CustomTabsIntent.KEY_PENDING_INTENT, pi); |
| menuItems.add(bundle); |
| } |
| customTabIntent.putParcelableArrayListExtra(CustomTabsIntent.EXTRA_MENU_ITEMS, menuItems); |
| return pi; |
| } |
| |
| private void addToolbarColorToIntent(Intent intent, int color) { |
| intent.putExtra(CustomTabsIntent.EXTRA_TOOLBAR_COLOR, color); |
| } |
| |
| /** |
| * Adds an action button to the custom tab toolbar. |
| * @return The {@link PendingIntent} that will be triggered when the action button is clicked. |
| */ |
| private PendingIntent addActionButtonToIntent(Intent intent, Bitmap icon, String description) { |
| PendingIntent pi = PendingIntent.getBroadcast( |
| InstrumentationRegistry.getTargetContext(), 0, new Intent(), 0); |
| intent.putExtra(CustomTabsIntent.EXTRA_ACTION_BUTTON_BUNDLE, |
| makeToolbarItemBundle(icon, description, pi)); |
| return pi; |
| } |
| |
| private Bundle makeToolbarItemBundle(Bitmap icon, String description, PendingIntent pi) { |
| Bundle bundle = new Bundle(); |
| bundle.putInt(CustomTabsIntent.KEY_ID, sIdToIncrement++); |
| bundle.putParcelable(CustomTabsIntent.KEY_ICON, icon); |
| bundle.putString(CustomTabsIntent.KEY_DESCRIPTION, description); |
| bundle.putParcelable(CustomTabsIntent.KEY_PENDING_INTENT, pi); |
| bundle.putBoolean(CustomButtonParams.SHOW_ON_TOOLBAR, true); |
| return bundle; |
| } |
| |
| private Bundle makeBottomBarBundle(int id, Bitmap icon, String description) { |
| Bundle bundle = new Bundle(); |
| PendingIntent pi = PendingIntent.getBroadcast( |
| InstrumentationRegistry.getTargetContext(), 0, new Intent(), 0); |
| |
| bundle.putInt(CustomTabsIntent.KEY_ID, sIdToIncrement++); |
| bundle.putString(CustomTabsIntent.KEY_DESCRIPTION, description); |
| bundle.putParcelable(CustomTabsIntent.KEY_PENDING_INTENT, pi); |
| bundle.putParcelable(CustomTabsIntent.KEY_ICON, icon); |
| return bundle; |
| } |
| |
| private Bundle makeUpdateVisualsBundle(int id, Bitmap icon, String description) { |
| Bundle bundle = new Bundle(); |
| bundle.putInt(CustomTabsIntent.KEY_ID, id); |
| bundle.putParcelable(CustomTabsIntent.KEY_ICON, icon); |
| bundle.putString(CustomTabsIntent.KEY_DESCRIPTION, description); |
| return bundle; |
| } |
| |
| private void openAppMenuAndAssertMenuShown() { |
| CustomTabsTestUtils.openAppMenuAndAssertMenuShown(mCustomTabActivityTestRule.getActivity()); |
| } |
| |
| /** |
| * @return The number of visible and enabled items in the given menu. |
| */ |
| private int getActualMenuSize(Menu menu) { |
| int actualMenuSize = 0; |
| for (int i = 0; i < menu.size(); i++) { |
| MenuItem item = menu.getItem(i); |
| if (item.isVisible() && item.isEnabled()) actualMenuSize++; |
| } |
| return actualMenuSize; |
| } |
| |
| /** |
| * @return The number of visible items in the given menu. |
| */ |
| private int getVisibleMenuSize(Menu menu) { |
| int visibleMenuSize = 0; |
| for (int i = 0; i < menu.size(); i++) { |
| MenuItem item = menu.getItem(i); |
| if (item.isVisible()) visibleMenuSize++; |
| } |
| return visibleMenuSize; |
| } |
| |
| private Bitmap createTestBitmap(int widthDp, int heightDp) { |
| Resources testRes = InstrumentationRegistry.getTargetContext().getResources(); |
| float density = testRes.getDisplayMetrics().density; |
| return Bitmap.createBitmap((int) (widthDp * density), |
| (int) (heightDp * density), Bitmap.Config.ARGB_8888); |
| } |
| |
| private Bitmap createVectorDrawableBitmap(@DrawableRes int resId, int widthDp, int heightDp) { |
| Context context = InstrumentationRegistry.getTargetContext(); |
| Drawable vectorDrawable = AppCompatResources.getDrawable(context, resId); |
| Bitmap bitmap = createTestBitmap(widthDp, heightDp); |
| Canvas canvas = new Canvas(bitmap); |
| float density = context.getResources().getDisplayMetrics().density; |
| int widthPx = (int) (density * widthDp); |
| int heightPx = (int) (density * heightDp); |
| vectorDrawable.setBounds(0, 0, widthPx, heightPx); |
| vectorDrawable.draw(canvas); |
| return bitmap; |
| } |
| |
| private static boolean isSelectPopupVisible(ChromeActivity activity) { |
| Tab tab = activity.getActivityTab(); |
| if (tab == null || tab.getWebContents() == null) return false; |
| return WebContentsUtils.isSelectPopupVisible(tab.getWebContents()); |
| } |
| |
| /** |
| * Test the entries in the context menu shown when long clicking an image. |
| * @SmallTest |
| * @RetryOnFailure |
| * BUG=crbug.com/655970 |
| */ |
| @Test |
| @DisabledTest |
| public void testContextMenuEntriesForImage() throws InterruptedException, TimeoutException { |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(createMinimalCustomTabIntent()); |
| |
| final int expectedMenuSize = 12; |
| Menu menu = ContextMenuUtils.openContextMenu( |
| mCustomTabActivityTestRule.getActivity().getActivityTab(), "logo"); |
| Assert.assertEquals(expectedMenuSize, menu.size()); |
| |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_copy_link_address)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_call)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_send_message)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_add_to_contacts)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_copy)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_copy_link_text)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_save_link_as)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_save_image)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_share_image)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_open_image)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_save_video)); |
| |
| Assert.assertTrue(menu.findItem(R.id.contextmenu_save_image).isVisible()); |
| Assert.assertTrue(menu.findItem(R.id.contextmenu_share_image).isVisible()); |
| Assert.assertTrue(menu.findItem(R.id.contextmenu_open_image).isVisible()); |
| Assert.assertTrue(menu.findItem(R.id.contextmenu_search_by_image).isVisible()); |
| |
| Assert.assertFalse(menu.findItem(R.id.contextmenu_copy_link_address).isVisible()); |
| Assert.assertFalse(menu.findItem(R.id.contextmenu_call).isVisible()); |
| Assert.assertFalse(menu.findItem(R.id.contextmenu_send_message).isVisible()); |
| Assert.assertFalse(menu.findItem(R.id.contextmenu_add_to_contacts).isVisible()); |
| Assert.assertFalse(menu.findItem(R.id.contextmenu_copy).isVisible()); |
| Assert.assertFalse(menu.findItem(R.id.contextmenu_copy_link_text).isVisible()); |
| Assert.assertFalse(menu.findItem(R.id.contextmenu_save_link_as).isVisible()); |
| Assert.assertFalse(menu.findItem(R.id.contextmenu_save_video).isVisible()); |
| } |
| |
| /** |
| * Test the entries in the context menu shown when long clicking a link. |
| * @SmallTest |
| * @RetryOnFailure |
| * BUG=crbug.com/655970 |
| */ |
| @Test |
| @DisabledTest |
| public void testContextMenuEntriesForLink() throws InterruptedException, TimeoutException { |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(createMinimalCustomTabIntent()); |
| |
| final int expectedMenuSize = 12; |
| Menu menu = ContextMenuUtils.openContextMenu( |
| mCustomTabActivityTestRule.getActivity().getActivityTab(), "aboutLink"); |
| Assert.assertEquals(expectedMenuSize, menu.size()); |
| |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_copy_link_address)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_call)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_send_message)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_add_to_contacts)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_copy)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_copy_link_text)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_save_link_as)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_save_image)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_share_image)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_open_image)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_save_video)); |
| |
| Assert.assertTrue(menu.findItem(R.id.contextmenu_copy_link_address).isVisible()); |
| Assert.assertTrue(menu.findItem(R.id.contextmenu_copy_link_text).isVisible()); |
| Assert.assertTrue(menu.findItem(R.id.contextmenu_save_link_as).isVisible()); |
| |
| Assert.assertFalse(menu.findItem(R.id.contextmenu_share_image).isVisible()); |
| Assert.assertFalse(menu.findItem(R.id.contextmenu_call).isVisible()); |
| Assert.assertFalse(menu.findItem(R.id.contextmenu_send_message).isVisible()); |
| Assert.assertFalse(menu.findItem(R.id.contextmenu_add_to_contacts).isVisible()); |
| Assert.assertFalse(menu.findItem(R.id.contextmenu_copy).isVisible()); |
| Assert.assertFalse(menu.findItem(R.id.contextmenu_save_image).isVisible()); |
| Assert.assertFalse(menu.findItem(R.id.contextmenu_open_image).isVisible()); |
| Assert.assertFalse(menu.findItem(R.id.contextmenu_search_by_image).isVisible()); |
| Assert.assertFalse(menu.findItem(R.id.contextmenu_save_video).isVisible()); |
| } |
| |
| /** |
| * Test the entries in the context menu shown when long clicking an mailto url. |
| * @SmallTest |
| * @RetryOnFailure |
| */ |
| @Test |
| @DisabledTest |
| public void testContextMenuEntriesForMailto() throws InterruptedException, TimeoutException { |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(createMinimalCustomTabIntent()); |
| |
| final int expectedMenuSize = 12; |
| Menu menu = ContextMenuUtils.openContextMenu( |
| mCustomTabActivityTestRule.getActivity().getActivityTab(), "email"); |
| Assert.assertEquals(expectedMenuSize, menu.size()); |
| |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_copy_link_address)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_call)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_send_message)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_add_to_contacts)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_copy)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_copy_link_text)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_save_link_as)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_save_image)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_share_image)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_open_image)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_save_video)); |
| |
| Assert.assertTrue(menu.findItem(R.id.contextmenu_send_message).isVisible()); |
| Assert.assertTrue(menu.findItem(R.id.contextmenu_add_to_contacts).isVisible()); |
| Assert.assertTrue(menu.findItem(R.id.contextmenu_copy).isVisible()); |
| |
| Assert.assertFalse(menu.findItem(R.id.contextmenu_copy_link_address).isVisible()); |
| Assert.assertFalse(menu.findItem(R.id.contextmenu_call).isVisible()); |
| Assert.assertFalse(menu.findItem(R.id.contextmenu_save_image).isVisible()); |
| Assert.assertFalse(menu.findItem(R.id.contextmenu_share_image).isVisible()); |
| Assert.assertFalse(menu.findItem(R.id.contextmenu_open_image).isVisible()); |
| Assert.assertFalse(menu.findItem(R.id.contextmenu_search_by_image).isVisible()); |
| Assert.assertFalse(menu.findItem(R.id.contextmenu_copy_link_text).isVisible()); |
| Assert.assertFalse(menu.findItem(R.id.contextmenu_save_link_as).isVisible()); |
| Assert.assertFalse(menu.findItem(R.id.contextmenu_save_video).isVisible()); |
| } |
| |
| /** |
| * Test the entries in the context menu shown when long clicking an tel url. |
| * @SmallTest |
| * @RetryOnFailure |
| */ |
| @Test |
| @DisabledTest |
| public void testContextMenuEntriesForTel() throws InterruptedException, TimeoutException { |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(createMinimalCustomTabIntent()); |
| |
| final int expectedMenuSize = 12; |
| Menu menu = ContextMenuUtils.openContextMenu( |
| mCustomTabActivityTestRule.getActivity().getActivityTab(), "tel"); |
| Assert.assertEquals(expectedMenuSize, menu.size()); |
| |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_copy_link_address)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_call)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_send_message)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_add_to_contacts)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_copy)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_copy_link_text)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_save_link_as)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_save_image)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_share_image)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_open_image)); |
| Assert.assertNotNull(menu.findItem(R.id.contextmenu_save_video)); |
| |
| Assert.assertTrue(menu.findItem(R.id.contextmenu_call).isVisible()); |
| Assert.assertTrue(menu.findItem(R.id.contextmenu_send_message).isVisible()); |
| Assert.assertTrue(menu.findItem(R.id.contextmenu_add_to_contacts).isVisible()); |
| Assert.assertTrue(menu.findItem(R.id.contextmenu_copy).isVisible()); |
| |
| Assert.assertFalse(menu.findItem(R.id.contextmenu_copy_link_address).isVisible()); |
| Assert.assertFalse(menu.findItem(R.id.contextmenu_save_image).isVisible()); |
| Assert.assertFalse(menu.findItem(R.id.contextmenu_share_image).isVisible()); |
| Assert.assertFalse(menu.findItem(R.id.contextmenu_open_image).isVisible()); |
| Assert.assertFalse(menu.findItem(R.id.contextmenu_search_by_image).isVisible()); |
| Assert.assertFalse(menu.findItem(R.id.contextmenu_copy_link_text).isVisible()); |
| Assert.assertFalse(menu.findItem(R.id.contextmenu_save_link_as).isVisible()); |
| Assert.assertFalse(menu.findItem(R.id.contextmenu_save_video).isVisible()); |
| } |
| |
| /** |
| * Test the entries in the app menu. |
| */ |
| @Test |
| @SmallTest |
| @RetryOnFailure |
| public void testAppMenu() throws Exception { |
| Intent intent = createMinimalCustomTabIntent(); |
| int numMenuEntries = 1; |
| addMenuEntriesToIntent(intent, numMenuEntries); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| |
| openAppMenuAndAssertMenuShown(); |
| Menu menu = mCustomTabActivityTestRule.getMenu(); |
| final int expectedMenuSize = numMenuEntries + NUM_CHROME_MENU_ITEMS; |
| |
| Assert.assertNotNull("App menu is not initialized: ", menu); |
| Assert.assertEquals(expectedMenuSize, getActualMenuSize(menu)); |
| Assert.assertEquals(expectedMenuSize, getVisibleMenuSize(menu)); |
| Assert.assertNotNull(menu.findItem(R.id.forward_menu_id)); |
| Assert.assertNotNull(menu.findItem(R.id.bookmark_this_page_id)); |
| Assert.assertNotNull(menu.findItem(R.id.offline_page_id)); |
| Assert.assertNotNull(menu.findItem(R.id.info_menu_id)); |
| Assert.assertNotNull(menu.findItem(R.id.reload_menu_id)); |
| Assert.assertNotNull(menu.findItem(R.id.open_in_browser_id)); |
| Assert.assertFalse(menu.findItem(R.id.share_row_menu_id).isVisible()); |
| Assert.assertFalse(menu.findItem(R.id.share_row_menu_id).isEnabled()); |
| Assert.assertNotNull(menu.findItem(R.id.find_in_page_id)); |
| Assert.assertNotNull(menu.findItem(R.id.add_to_homescreen_id)); |
| Assert.assertNotNull(menu.findItem(R.id.request_desktop_site_row_menu_id)); |
| } |
| |
| /** |
| * Test the entries in app menu for media viewer. |
| */ |
| @Test |
| @SmallTest |
| @RetryOnFailure |
| public void testAppMenuForMediaViewer() throws Exception { |
| Intent intent = createMinimalCustomTabIntent(); |
| intent.putExtra(CustomTabIntentDataProvider.EXTRA_UI_TYPE, |
| CustomTabIntentDataProvider.CustomTabsUiType.MEDIA_VIEWER); |
| IntentHandler.addTrustedIntentExtras(intent); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| |
| openAppMenuAndAssertMenuShown(); |
| Menu menu = mCustomTabActivityTestRule.getMenu(); |
| final int expectedMenuSize = 0; |
| |
| Assert.assertNotNull("App menu is not initialized: ", menu); |
| Assert.assertEquals(expectedMenuSize, getActualMenuSize(menu)); |
| Assert.assertEquals(expectedMenuSize, getVisibleMenuSize(menu)); |
| } |
| |
| /** |
| * Test the entries in app menu for Reader Mode. |
| */ |
| @Test |
| @SmallTest |
| @RetryOnFailure |
| public void testAppMenuForReaderMode() throws Exception { |
| Intent intent = createMinimalCustomTabIntent(); |
| intent.putExtra(CustomTabIntentDataProvider.EXTRA_UI_TYPE, |
| CustomTabIntentDataProvider.CustomTabsUiType.READER_MODE); |
| IntentHandler.addTrustedIntentExtras(intent); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| |
| openAppMenuAndAssertMenuShown(); |
| Menu menu = mCustomTabActivityTestRule.getMenu(); |
| final int expectedMenuSize = 2; |
| |
| Assert.assertNotNull("App menu is not initialized: ", menu); |
| Assert.assertEquals(expectedMenuSize, getActualMenuSize(menu)); |
| Assert.assertEquals(expectedMenuSize, getVisibleMenuSize(menu)); |
| Assert.assertTrue(menu.findItem(R.id.find_in_page_id).isVisible()); |
| Assert.assertTrue(menu.findItem(R.id.reader_mode_prefs_id).isVisible()); |
| } |
| |
| /** |
| * Test the entries in app menu for media viewer. |
| */ |
| @Test |
| @SmallTest |
| @RetryOnFailure |
| public void testAppMenuForOfflinePage() throws Exception { |
| Intent intent = createMinimalCustomTabIntent(); |
| intent.putExtra(CustomTabIntentDataProvider.EXTRA_UI_TYPE, |
| CustomTabIntentDataProvider.CustomTabsUiType.OFFLINE_PAGE); |
| IntentHandler.addTrustedIntentExtras(intent); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| |
| openAppMenuAndAssertMenuShown(); |
| Menu menu = mCustomTabActivityTestRule.getMenu(); |
| final int expectedMenuSize = 3; |
| |
| Assert.assertNotNull("App menu is not initialized: ", menu); |
| Assert.assertEquals(expectedMenuSize, getActualMenuSize(menu)); |
| Assert.assertEquals(expectedMenuSize, getVisibleMenuSize(menu)); |
| Assert.assertTrue(menu.findItem(R.id.find_in_page_id).isVisible()); |
| Assert.assertNotNull(menu.findItem(R.id.request_desktop_site_row_menu_id)); |
| |
| MenuItem icon_row = menu.findItem(R.id.icon_row_menu_id); |
| Assert.assertNotNull(icon_row); |
| Assert.assertNotNull(icon_row.hasSubMenu()); |
| SubMenu icon_row_menu = icon_row.getSubMenu(); |
| final int expectedIconMenuSize = 4; |
| Assert.assertEquals(expectedIconMenuSize, getVisibleMenuSize(icon_row_menu)); |
| Assert.assertNotNull(icon_row_menu.findItem(R.id.forward_menu_id)); |
| Assert.assertNotNull(icon_row_menu.findItem(R.id.bookmark_this_page_id)); |
| Assert.assertNotNull(icon_row_menu.findItem(R.id.info_menu_id)); |
| Assert.assertNotNull(icon_row_menu.findItem(R.id.reload_menu_id)); |
| } |
| |
| /** |
| * Tests if the default share item can be shown in the app menu. |
| */ |
| @Test |
| @SmallTest |
| @RetryOnFailure |
| public void testShareMenuItem() throws Exception { |
| Intent intent = createMinimalCustomTabIntent(); |
| intent.putExtra(CustomTabsIntent.EXTRA_DEFAULT_SHARE_MENU_ITEM, true); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| |
| openAppMenuAndAssertMenuShown(); |
| Menu menu = mCustomTabActivityTestRule.getMenu(); |
| Assert.assertTrue(menu.findItem(R.id.share_menu_id).isVisible()); |
| Assert.assertTrue(menu.findItem(R.id.share_menu_id).isEnabled()); |
| } |
| |
| |
| /** |
| * Test that only up to 5 entries are added to the custom menu. |
| */ |
| @Test |
| @SmallTest |
| @RetryOnFailure |
| public void testMaxMenuItems() throws Exception { |
| Intent intent = createMinimalCustomTabIntent(); |
| int numMenuEntries = 7; |
| Assert.assertTrue(MAX_MENU_CUSTOM_ITEMS < numMenuEntries); |
| addMenuEntriesToIntent(intent, numMenuEntries); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| |
| openAppMenuAndAssertMenuShown(); |
| Menu menu = mCustomTabActivityTestRule.getMenu(); |
| final int expectedMenuSize = MAX_MENU_CUSTOM_ITEMS + NUM_CHROME_MENU_ITEMS; |
| Assert.assertNotNull("App menu is not initialized: ", menu); |
| Assert.assertEquals(expectedMenuSize, getActualMenuSize(menu)); |
| Assert.assertEquals(expectedMenuSize, getVisibleMenuSize(menu)); |
| } |
| |
| /** |
| * Test whether the custom menu is correctly shown and clicking it sends the right |
| * {@link PendingIntent}. |
| */ |
| @Test |
| @SmallTest |
| @RetryOnFailure |
| public void testCustomMenuEntry() throws InterruptedException, TimeoutException { |
| Intent customTabIntent = createMinimalCustomTabIntent(); |
| Intent baseCallbackIntent = new Intent(); |
| baseCallbackIntent.putExtra("FOO", 42); |
| final PendingIntent pi = addMenuEntriesToIntent(customTabIntent, 1, baseCallbackIntent); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(customTabIntent); |
| |
| final OnFinishedForTest onFinished = new OnFinishedForTest(pi); |
| getActivity().getIntentDataProvider().setPendingIntentOnFinishedForTesting(onFinished); |
| |
| openAppMenuAndAssertMenuShown(); |
| ThreadUtils.runOnUiThread(() -> { |
| MenuItem item = getActivity().getAppMenuPropertiesDelegate().getMenuItemForTitle( |
| TEST_MENU_TITLE); |
| Assert.assertNotNull(item); |
| Assert.assertTrue(getActivity().onOptionsItemSelected(item)); |
| }); |
| |
| onFinished.waitForCallback("Pending Intent was not sent."); |
| Intent callbackIntent = onFinished.getCallbackIntent(); |
| Assert.assertThat(callbackIntent.getDataString(), equalTo(mTestPage)); |
| |
| // Verify that the callback intent has the page title as the subject, but other extras are |
| // kept intact. |
| Assert.assertThat( |
| callbackIntent.getStringExtra(Intent.EXTRA_SUBJECT), equalTo("The Google")); |
| Assert.assertThat(callbackIntent.getIntExtra("FOO", 0), equalTo(42)); |
| } |
| |
| /** |
| * Test whether clicking "Open in Chrome" takes us to a chrome normal tab, loading the same url. |
| */ |
| @Test |
| @SmallTest |
| @RetryOnFailure |
| public void testOpenInBrowser() throws Exception { |
| // Augment the CustomTabsSession to catch the callback. |
| CallbackHelper callbackTriggered = new CallbackHelper(); |
| CustomTabsSession session = CustomTabsTestUtils.bindWithCallback(new CustomTabsCallback() { |
| @Override |
| public void extraCallback(String callbackName, Bundle args) { |
| if (callbackName.equals(CustomTabsConnection.OPEN_IN_BROWSER_CALLBACK)) { |
| callbackTriggered.notifyCalled(); |
| } |
| } |
| }); |
| |
| Intent intent = new CustomTabsIntent.Builder(session).build().intent; |
| intent.setData(Uri.parse(mTestPage)); |
| intent.setComponent(new ComponentName( |
| InstrumentationRegistry.getTargetContext(), ChromeLauncherActivity.class)); |
| intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| |
| IntentFilter filter = new IntentFilter(Intent.ACTION_VIEW); |
| filter.addDataScheme(Uri.parse(mTestServer.getURL("/")).getScheme()); |
| final ActivityMonitor monitor = |
| InstrumentationRegistry.getInstrumentation().addMonitor(filter, null, false); |
| openAppMenuAndAssertMenuShown(); |
| final String menuItemTitle = getActivity().getString(R.string.menu_open_in_product_default); |
| ThreadUtils.runOnUiThread(() -> { |
| MenuItem item = getActivity().getAppMenuHandler().getAppMenu().getMenu().findItem( |
| R.id.open_in_browser_id); |
| Assert.assertNotNull(item); |
| Assert.assertEquals(menuItemTitle, item.getTitle().toString()); |
| getActivity().onMenuOrKeyboardAction(R.id.open_in_browser_id, false); |
| }); |
| CriteriaHelper.pollInstrumentationThread(new Criteria() { |
| @Override |
| public boolean isSatisfied() { |
| return InstrumentationRegistry.getInstrumentation().checkMonitorHit(monitor, 1); |
| } |
| }); |
| |
| callbackTriggered.waitForCallback(0); |
| } |
| |
| /** |
| * Test whether a custom tab can be reparented to a new activity. |
| */ |
| @Test |
| @SmallTest |
| @RetryOnFailure |
| public void testTabReparentingBasic() throws InterruptedException { |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(createMinimalCustomTabIntent()); |
| reparentAndVerifyTab(); |
| } |
| |
| /** |
| * Test whether a custom tab can be reparented to a new activity while showing an infobar. |
| * |
| * TODO(timloh): Use a different InfoBar type once we only use modals for permission prompts. |
| */ |
| @Test |
| @SmallTest |
| @DisableFeatures(ChromeFeatureList.MODAL_PERMISSION_PROMPTS) |
| @RetryOnFailure |
| public void testTabReparentingInfoBar() throws InterruptedException { |
| LocationSettingsTestUtil.setSystemLocationSettingEnabled(true); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent( |
| CustomTabsTestUtils.createMinimalCustomTabIntent( |
| InstrumentationRegistry.getTargetContext(), |
| mTestServer.getURL(GEOLOCATION_PAGE))); |
| CriteriaHelper.pollUiThread( |
| () -> isInfoBarSizeOne(mCustomTabActivityTestRule.getActivity().getActivityTab())); |
| |
| ChromeActivity newActivity = reparentAndVerifyTab(); |
| CriteriaHelper.pollUiThread(() -> isInfoBarSizeOne(newActivity.getActivityTab())); |
| } |
| |
| private static boolean isInfoBarSizeOne(Tab tab) { |
| if (tab == null) return false; |
| InfoBarContainer container = InfoBarContainer.get(tab); |
| if (container == null) return false; |
| return container.getInfoBarsForTesting().size() == 1; |
| } |
| |
| /** |
| * Test whether a custom tab can be reparented to a new activity while showing a select popup. |
| */ |
| // @SmallTest |
| // @RetryOnFailure |
| @Test |
| @DisabledTest // Disabled due to flakiness on browser_side_navigation apk - see crbug.com/707766 |
| public void testTabReparentingSelectPopup() throws InterruptedException, TimeoutException { |
| LocationSettingsTestUtil.setSystemLocationSettingEnabled(true); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent( |
| CustomTabsTestUtils.createMinimalCustomTabIntent( |
| InstrumentationRegistry.getTargetContext(), |
| mTestServer.getURL(SELECT_POPUP_PAGE))); |
| CriteriaHelper.pollUiThread(new Criteria() { |
| @Override |
| public boolean isSatisfied() { |
| Tab currentTab = mCustomTabActivityTestRule.getActivity().getActivityTab(); |
| return currentTab != null && currentTab.getWebContents() != null; |
| } |
| }); |
| DOMUtils.clickNode(mCustomTabActivityTestRule.getWebContents(), "select"); |
| CriteriaHelper.pollUiThread(new Criteria() { |
| @Override |
| public boolean isSatisfied() { |
| return isSelectPopupVisible(mCustomTabActivityTestRule.getActivity()); |
| } |
| }); |
| final ChromeActivity newActivity = reparentAndVerifyTab(); |
| CriteriaHelper.pollUiThread(new Criteria() { |
| @Override |
| public boolean isSatisfied() { |
| return isSelectPopupVisible(newActivity); |
| } |
| }); |
| } |
| /** |
| * Test whether the color of the toolbar is correctly customized. For L or later releases, |
| * status bar color is also tested. |
| */ |
| @Test |
| @SmallTest |
| @RetryOnFailure |
| public void testToolbarColor() throws InterruptedException { |
| Intent intent = createMinimalCustomTabIntent(); |
| final int expectedColor = Color.RED; |
| addToolbarColorToIntent(intent, expectedColor); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| |
| View toolbarView = mCustomTabActivityTestRule.getActivity().findViewById(R.id.toolbar); |
| Assert.assertTrue( |
| "A custom tab toolbar is never shown", toolbarView instanceof CustomTabToolbar); |
| CustomTabToolbar toolbar = (CustomTabToolbar) toolbarView; |
| Assert.assertEquals(expectedColor, toolbar.getBackground().getColor()); |
| Assert.assertFalse(mCustomTabActivityTestRule.getActivity() |
| .getToolbarManager() |
| .getLocationBarModelForTesting() |
| .shouldEmphasizeHttpsScheme()); |
| // TODO(https://crbug.com/871805): Use helper class to determine whether dark status icons |
| // are supported. |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
| Assert.assertEquals(expectedColor, |
| mCustomTabActivityTestRule.getActivity().getWindow().getStatusBarColor()); |
| } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) { |
| Assert.assertEquals(ColorUtils.getDarkenedColorForStatusBar(expectedColor), |
| mCustomTabActivityTestRule.getActivity().getWindow().getStatusBarColor()); |
| } |
| } |
| |
| /** |
| * Test if an action button is shown with correct image and size, and clicking it sends the |
| * correct {@link PendingIntent}. |
| */ |
| @Test |
| @SmallTest |
| @Feature({"UiCatalogue"}) |
| @RetryOnFailure |
| public void testActionButton() throws InterruptedException, TimeoutException { |
| Bitmap expectedIcon = createVectorDrawableBitmap(R.drawable.ic_credit_card_black, 77, 48); |
| Intent intent = createMinimalCustomTabIntent(); |
| final PendingIntent pi = addActionButtonToIntent(intent, expectedIcon, "Good test"); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| |
| final OnFinishedForTest onFinished = new OnFinishedForTest(pi); |
| getActivity().getIntentDataProvider().setPendingIntentOnFinishedForTesting(onFinished); |
| |
| View toolbarView = mCustomTabActivityTestRule.getActivity().findViewById(R.id.toolbar); |
| Assert.assertTrue( |
| "A custom tab toolbar is never shown", toolbarView instanceof CustomTabToolbar); |
| CustomTabToolbar toolbar = (CustomTabToolbar) toolbarView; |
| final ImageButton actionButton = toolbar.getCustomActionButtonForTest(0); |
| |
| Assert.assertNotNull(actionButton); |
| Assert.assertNotNull(actionButton.getDrawable()); |
| Assert.assertTrue("Action button's background is not a BitmapDrawable.", |
| actionButton.getDrawable() instanceof BitmapDrawable); |
| |
| Assert.assertTrue("Action button does not have the correct bitmap.", |
| expectedIcon.sameAs(((BitmapDrawable) actionButton.getDrawable()).getBitmap())); |
| |
| mScreenShooter.shoot("Action Buttons"); |
| |
| ThreadUtils.runOnUiThreadBlocking((Runnable) actionButton::performClick); |
| |
| onFinished.waitForCallback("Pending Intent was not sent."); |
| Assert.assertThat(onFinished.getCallbackIntent().getDataString(), equalTo(mTestPage)); |
| } |
| |
| /** |
| * Test if an action button is shown with correct image and size, and clicking it sends the |
| * correct {@link PendingIntent}. |
| */ |
| @Test |
| @SmallTest |
| @Feature({"UiCatalogue"}) |
| @RetryOnFailure |
| public void testMultipleActionButtons() throws InterruptedException, TimeoutException { |
| Bitmap expectedIcon1 = createVectorDrawableBitmap(R.drawable.ic_content_copy_black, 48, 48); |
| Bitmap expectedIcon2 = createVectorDrawableBitmap(R.drawable.ic_music_note_36dp, 48, 48); |
| Intent intent = createMinimalCustomTabIntent(); |
| |
| // Mark the intent as trusted so it can show more than one action button. |
| IntentHandler.addTrustedIntentExtras(intent); |
| Assert.assertTrue(IntentHandler.notSecureIsIntentChromeOrFirstParty(intent)); |
| |
| ArrayList<Bundle> toolbarItems = new ArrayList<>(2); |
| final PendingIntent pi1 = PendingIntent.getBroadcast( |
| InstrumentationRegistry.getTargetContext(), 0, new Intent(), 0); |
| final OnFinishedForTest onFinished1 = new OnFinishedForTest(pi1); |
| toolbarItems.add(makeToolbarItemBundle(expectedIcon1, "Good test", pi1)); |
| final PendingIntent pi2 = PendingIntent.getBroadcast( |
| InstrumentationRegistry.getTargetContext(), 1, new Intent(), 0); |
| Assert.assertThat(pi2, not(equalTo(pi1))); |
| final OnFinishedForTest onFinished2 = new OnFinishedForTest(pi2); |
| toolbarItems.add(makeToolbarItemBundle(expectedIcon2, "Even gooder test", pi2)); |
| intent.putParcelableArrayListExtra(CustomTabsIntent.EXTRA_TOOLBAR_ITEMS, toolbarItems); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| |
| // Forward the onFinished event to both objects. |
| getActivity().getIntentDataProvider().setPendingIntentOnFinishedForTesting( |
| (pendingIntent, openedIntent, resultCode, resultData, resultExtras) -> { |
| onFinished1.onSendFinished( |
| pendingIntent, openedIntent, resultCode, resultData, resultExtras); |
| onFinished2.onSendFinished( |
| pendingIntent, openedIntent, resultCode, resultData, resultExtras); |
| }); |
| |
| View toolbarView = mCustomTabActivityTestRule.getActivity().findViewById(R.id.toolbar); |
| Assert.assertTrue( |
| "A custom tab toolbar is never shown", toolbarView instanceof CustomTabToolbar); |
| CustomTabToolbar toolbar = (CustomTabToolbar) toolbarView; |
| final ImageButton actionButton = toolbar.getCustomActionButtonForTest(1); |
| |
| Assert.assertNotNull("Action button not found", actionButton); |
| Assert.assertNotNull(actionButton.getDrawable()); |
| Assert.assertTrue("Action button's background is not a BitmapDrawable.", |
| actionButton.getDrawable() instanceof BitmapDrawable); |
| |
| Assert.assertTrue("Action button does not have the correct bitmap.", |
| expectedIcon1.sameAs(((BitmapDrawable) actionButton.getDrawable()).getBitmap())); |
| |
| mScreenShooter.shoot("Multiple Action Buttons"); |
| |
| ThreadUtils.runOnUiThreadBlocking((Runnable) actionButton::performClick); |
| |
| onFinished1.waitForCallback("Pending Intent was not sent."); |
| Assert.assertThat(onFinished1.getCallbackIntent().getDataString(), equalTo(mTestPage)); |
| Assert.assertNull(onFinished2.getCallbackIntent()); |
| |
| CustomTabsConnection connection = CustomTabsConnection.getInstance(); |
| int id = toolbarItems.get(0).getInt(CustomTabsIntent.KEY_ID); |
| Bundle updateActionButtonBundle = |
| makeUpdateVisualsBundle(id, expectedIcon2, "Bestest testest"); |
| Bundle updateVisualsBundle = new Bundle(); |
| updateVisualsBundle.putParcelableArrayList(CustomTabsIntent.EXTRA_TOOLBAR_ITEMS, |
| new ArrayList<>(Arrays.asList(updateActionButtonBundle))); |
| CustomTabsSessionToken token = CustomTabsSessionToken.getSessionTokenFromIntent(intent); |
| Assert.assertTrue(connection.updateVisuals(token, updateVisualsBundle)); |
| |
| Assert.assertEquals("Bestest testest", actionButton.getContentDescription()); |
| } |
| |
| /** |
| * Test that additional action buttons are ignored for untrusted intents. |
| */ |
| @Test |
| @SmallTest |
| @Feature({"UiCatalogue"}) |
| @RetryOnFailure |
| public void testMultipleActionButtons_untrusted() |
| throws InterruptedException, TimeoutException { |
| Bitmap expectedIcon1 = createVectorDrawableBitmap(R.drawable.ic_content_copy_black, 48, 48); |
| Bitmap expectedIcon2 = createVectorDrawableBitmap(R.drawable.ic_music_note_36dp, 48, 48); |
| Intent intent = createMinimalCustomTabIntent(); |
| |
| // By default, the intent should not be trusted. |
| Assert.assertFalse(IntentHandler.notSecureIsIntentChromeOrFirstParty(intent)); |
| |
| ArrayList<Bundle> toolbarItems = new ArrayList<>(2); |
| final PendingIntent pi = PendingIntent.getBroadcast( |
| InstrumentationRegistry.getTargetContext(), 0, new Intent(), 0); |
| toolbarItems.add(makeToolbarItemBundle(expectedIcon1, "Shown", pi)); |
| toolbarItems.add(makeToolbarItemBundle(expectedIcon2, "Not shown", pi)); |
| intent.putParcelableArrayListExtra(CustomTabsIntent.EXTRA_TOOLBAR_ITEMS, toolbarItems); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| |
| View toolbarView = mCustomTabActivityTestRule.getActivity().findViewById(R.id.toolbar); |
| Assert.assertTrue( |
| "A custom tab toolbar is never shown", toolbarView instanceof CustomTabToolbar); |
| CustomTabToolbar toolbar = (CustomTabToolbar) toolbarView; |
| final ImageButton actionButton = toolbar.getCustomActionButtonForTest(0); |
| Assert.assertNotNull("Action button not found", actionButton); |
| Assert.assertEquals("Shown", actionButton.getContentDescription()); |
| |
| Assert.assertNull(toolbar.getCustomActionButtonForTest(1)); |
| } |
| |
| /** |
| * Test the case that the action button should not be shown, given a bitmap with unacceptable |
| * height/width ratio. |
| */ |
| @Test |
| @SmallTest |
| @RetryOnFailure |
| public void testActionButtonBadRatio() throws InterruptedException { |
| Bitmap expectedIcon = createTestBitmap(60, 20); |
| Intent intent = createMinimalCustomTabIntent(); |
| addActionButtonToIntent(intent, expectedIcon, "Good test"); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| |
| View toolbarView = mCustomTabActivityTestRule.getActivity().findViewById(R.id.toolbar); |
| Assert.assertTrue( |
| "A custom tab toolbar is never shown", toolbarView instanceof CustomTabToolbar); |
| CustomTabToolbar toolbar = (CustomTabToolbar) toolbarView; |
| final ImageButton actionButton = toolbar.getCustomActionButtonForTest(0); |
| |
| Assert.assertNull("Action button should not be shown", actionButton); |
| |
| CustomTabIntentDataProvider dataProvider = getActivity().getIntentDataProvider(); |
| Assert.assertThat(dataProvider.getCustomButtonsOnToolbar(), is(empty())); |
| } |
| |
| @Test |
| @SmallTest |
| @RetryOnFailure |
| public void testBottomBar() throws InterruptedException { |
| final int numItems = 3; |
| final Bitmap expectedIcon = createTestBitmap(48, 24); |
| final int barColor = Color.GREEN; |
| |
| Intent intent = createMinimalCustomTabIntent(); |
| ArrayList<Bundle> bundles = new ArrayList<>(); |
| for (int i = 1; i <= numItems; i++) { |
| Bundle bundle = makeBottomBarBundle(i, expectedIcon, Integer.toString(i)); |
| bundles.add(bundle); |
| } |
| intent.putExtra(CustomTabsIntent.EXTRA_TOOLBAR_ITEMS, bundles); |
| intent.putExtra(CustomTabsIntent.EXTRA_SECONDARY_TOOLBAR_COLOR, barColor); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| |
| ViewGroup bottomBar = mCustomTabActivityTestRule.getActivity().findViewById( |
| R.id.custom_tab_bottom_bar_wrapper); |
| Assert.assertNotNull(bottomBar); |
| Assert.assertTrue("Bottom Bar wrapper is not visible.", |
| bottomBar.getVisibility() == View.VISIBLE && bottomBar.getHeight() > 0 |
| && bottomBar.getWidth() > 0); |
| Assert.assertEquals("Bottom Bar showing incorrect number of buttons.", numItems, |
| bottomBar.getChildCount()); |
| Assert.assertEquals("Bottom bar not showing correct color", barColor, |
| ((ColorDrawable) bottomBar.getBackground()).getColor()); |
| for (int i = 0; i < numItems; i++) { |
| ImageButton button = (ImageButton) bottomBar.getChildAt(i); |
| Assert.assertTrue("Bottom Bar button does not have the correct bitmap.", |
| expectedIcon.sameAs(((BitmapDrawable) button.getDrawable()).getBitmap())); |
| Assert.assertTrue("Bottom Bar button is not visible.", |
| button.getVisibility() == View.VISIBLE && button.getHeight() > 0 |
| && button.getWidth() > 0); |
| Assert.assertEquals("Bottom Bar button does not have correct content description", |
| Integer.toString(i + 1), button.getContentDescription()); |
| } |
| } |
| |
| private void runAndWaitForActivityStopped(Runnable runnable) |
| throws TimeoutException, InterruptedException { |
| CallbackHelper cctHiddenCallback = new CallbackHelper(); |
| ActivityStateListener listener = (activity, newState) -> { |
| if (activity == mCustomTabActivityTestRule.getActivity() |
| && (newState == ActivityState.STOPPED || newState == ActivityState.DESTROYED)) { |
| cctHiddenCallback.notifyCalled(); |
| } |
| }; |
| ApplicationStatus.registerStateListenerForAllActivities(listener); |
| |
| runnable.run(); |
| cctHiddenCallback.waitForCallback("Hide cct", 0); |
| ApplicationStatus.unregisterActivityStateListener(listener); |
| } |
| |
| /** |
| This test executes the following workflow assuming dynamic module has been loaded succesfully: |
| - moduleManagedUrl1 -> nav1.1 -> nav1.2 -> modulemanagedUrl2 -> nav2.1 -> nav2.2 |
| - User hits the "close button", therefore goes back to modulemanagedUrl2 |
| - User hits the Android back button, going returning to nav1.2 |
| - User hits the "close button" again, going return to moduleManagedUrl1 |
| - User hits the Android back button thereby closes CCT. |
| */ |
| @Test |
| @SmallTest |
| @EnableFeatures(ChromeFeatureList.CCT_MODULE) |
| public void testCloseButtonBehaviourWithDynamicModule() |
| throws InterruptedException, ExecutionException, TimeoutException { |
| setupHttpsTestServerAndPages(); |
| String relativeUrl1 = "/chrome/test/data/android/about.html"; |
| String moduleManagedUrl1 = mTestServer.getURL(relativeUrl1); |
| String relativeUrl2 = "/chrome/test/data/android/simple.html"; |
| String moduleManagedUrl2 = mTestServer.getURL(relativeUrl2); |
| |
| Intent intent = CustomTabsDynamicModuleTestUtils.makeDynamicModuleIntent( |
| moduleManagedUrl1, "^(" + relativeUrl1 + "|" + relativeUrl2 + ")$"); |
| |
| // Open CCT with moduleManagedUrl1 and navigate |
| // moduleManagedUrl1 -> nav1.1 - nav1.2 -> modulemanagedUrl2 -> nav2.1 -> nav2.2 |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| CustomTabActivity cctActivity = mCustomTabActivityTestRule.getActivity(); |
| |
| mCustomTabActivityTestRule.loadUrlInTab(mTestPage, PageTransition.LINK, |
| cctActivity.getActivityTab()); |
| mCustomTabActivityTestRule.loadUrlInTab(mTestPage2, PageTransition.LINK, |
| cctActivity.getActivityTab()); |
| mCustomTabActivityTestRule.loadUrlInTab(moduleManagedUrl2, PageTransition.TYPED, |
| cctActivity.getActivityTab()); |
| mCustomTabActivityTestRule.loadUrlInTab(mTestPage, PageTransition.LINK, |
| cctActivity.getActivityTab()); |
| mCustomTabActivityTestRule.loadUrlInTab(mTestPage2, PageTransition.LINK, |
| cctActivity.getActivityTab()); |
| |
| // click the close button and wait while tab page loaded |
| ClickUtils.clickButton(cctActivity.findViewById(R.id.close_button)); |
| ChromeTabUtils.waitForTabPageLoaded(cctActivity.getActivityTab(), (String) null); |
| |
| // close button returns back to moduleManagedUrl2 |
| Assert.assertEquals(moduleManagedUrl2, cctActivity.getActivityTab().getUrl()); |
| |
| // press the back button and wait while tab page loaded |
| UiDevice mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); |
| mDevice.pressBack(); |
| ChromeTabUtils.waitForTabPageLoaded(cctActivity.getActivityTab(), (String) null); |
| |
| // the back button returns to nav1.2 |
| Assert.assertEquals(mTestPage2, cctActivity.getActivityTab().getUrl()); |
| |
| // click the close button and wait while tab page loaded |
| ClickUtils.clickButton(cctActivity.findViewById(R.id.close_button)); |
| ChromeTabUtils.waitForTabPageLoaded(cctActivity.getActivityTab(), (String) null); |
| |
| // close button returns back to moduleManagedUrl1 |
| Assert.assertEquals(moduleManagedUrl1, cctActivity.getActivityTab().getUrl()); |
| |
| // press back button and while cct is hidden |
| runAndWaitForActivityStopped(mDevice::pressBack); |
| } |
| |
| /** |
| This test executes the following workflow assuming dynamic module has not been loaded: |
| - moduleManagedUrl1 -> nav1.1 - nav1.2 -> modulemanagedUrl2 -> nav2.1 -> nav2.2 |
| - User hits the close button, thereby closes CCT |
| */ |
| @Test |
| @SmallTest |
| public void testCloseButtonBehaviourWithoutDynamicModule() |
| throws InterruptedException, ExecutionException, TimeoutException { |
| setupHttpsTestServerAndPages(); |
| String moduleManagedUrl1 = mTestServer.getURL( |
| "/chrome/test/data/android/about.html"); |
| String moduleManagedUrl2 = mTestServer.getURL( |
| "/chrome/test/data/android/simple.html"); |
| |
| // Open CCT with moduleManagedUrl1 and navigate |
| // moduleManagedUrl1 -> nav1.1 - nav1.2 -> modulemanagedUrl2 -> nav2.1 -> nav2.2 |
| |
| Intent intent = CustomTabsTestUtils.createMinimalCustomTabIntent( |
| InstrumentationRegistry.getTargetContext(), moduleManagedUrl1); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| CustomTabActivity cctActivity = mCustomTabActivityTestRule.getActivity(); |
| |
| mCustomTabActivityTestRule.loadUrlInTab(mTestPage, PageTransition.LINK, |
| cctActivity.getActivityTab()); |
| mCustomTabActivityTestRule.loadUrlInTab(mTestPage2, PageTransition.LINK, |
| cctActivity.getActivityTab()); |
| mCustomTabActivityTestRule.loadUrlInTab(moduleManagedUrl2, PageTransition.LINK, |
| cctActivity.getActivityTab()); |
| mCustomTabActivityTestRule.loadUrlInTab(mTestPage, PageTransition.LINK, |
| cctActivity.getActivityTab()); |
| mCustomTabActivityTestRule.loadUrlInTab(mTestPage2, PageTransition.LINK, |
| cctActivity.getActivityTab()); |
| |
| // click close button and wait while cct is hidden |
| runAndWaitForActivityStopped(() -> |
| ClickUtils.clickButton(cctActivity.findViewById(R.id.close_button))); |
| } |
| |
| /** |
| This test executes the following workflow assuming dynamic module loading fails: |
| - moduleManagedUrl1 -> nav1.1 - nav1.2 |
| - User hits the close button, thereby closes CCT |
| */ |
| @Test |
| @SmallTest |
| public void testCloseButtonBehaviourDynamicModuleLoadFails() |
| throws InterruptedException, ExecutionException, TimeoutException { |
| setupHttpsTestServerAndPages(); |
| String relativeUrl = "/chrome/test/data/android/about.html"; |
| String moduleManagedUrl = mTestServer.getURL(relativeUrl); |
| |
| // Open CCT with moduleManagedUrl1 and navigate |
| // moduleManagedUrl1 -> nav1.1 - nav1.2 |
| |
| // Make an intent with nonexistent class name so module loading fails. |
| ComponentName componentName = |
| new ComponentName(CustomTabsDynamicModuleTestUtils.FAKE_MODULE_PACKAGE_NAME, |
| "ClassName"); |
| Intent intent = CustomTabsDynamicModuleTestUtils.makeDynamicModuleIntent( |
| componentName, moduleManagedUrl, "^(" + relativeUrl + ")$"); |
| |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| CustomTabActivity cctActivity = mCustomTabActivityTestRule.getActivity(); |
| |
| // Wait until ModuleLoader tries to load a module. |
| CriteriaHelper.pollUiThread(new Criteria() { |
| @Override |
| public boolean isSatisfied() { |
| return !cctActivity.getComponent() |
| .resolveDynamicModuleCoordinator().isModuleLoading(); |
| } |
| }); |
| |
| mCustomTabActivityTestRule.loadUrlInTab(mTestPage, PageTransition.LINK, |
| cctActivity.getActivityTab()); |
| mCustomTabActivityTestRule.loadUrlInTab(mTestPage2, PageTransition.LINK, |
| cctActivity.getActivityTab()); |
| |
| // click close button and wait while cct is hidden |
| runAndWaitForActivityStopped(() -> |
| ClickUtils.clickButton(cctActivity.findViewById(R.id.close_button))); |
| } |
| |
| @Test |
| @SmallTest |
| @EnableFeatures(ChromeFeatureList.CCT_MODULE) |
| public void testSetTopBarContentView() throws Exception { |
| setupHttpsTestServerAndPages(); |
| String moduleManagedUrl = mTestServer.getURL("/chrome/test/data/android/about.html"); |
| Intent intent = |
| CustomTabsDynamicModuleTestUtils.makeDynamicModuleIntent(moduleManagedUrl, null); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| waitForModuleLoading(); |
| |
| ThreadUtils.runOnUiThread(() -> { |
| CustomTabActivity cctActivity = mCustomTabActivityTestRule.getActivity(); |
| View anyView = new View(cctActivity); |
| cctActivity.getComponent().resolveDynamicModuleCoordinator() |
| .setTopBarContentView(anyView); |
| ViewGroup topBar = cctActivity.findViewById(R.id.topbar); |
| Assert.assertNotNull(topBar); |
| Assert.assertThat(anyView.getParent(), equalTo(topBar)); |
| Assert.assertEquals(View.GONE, anyView.getVisibility()); |
| }); |
| } |
| |
| @Test |
| @SmallTest |
| @EnableFeatures(ChromeFeatureList.CCT_MODULE) |
| public void testSetTopBarContentView_secondCallIsNoOp() throws Exception { |
| setupHttpsTestServerAndPages(); |
| String moduleManagedUrl = mTestServer.getURL("/chrome/test/data/android/about.html"); |
| Intent intent = |
| CustomTabsDynamicModuleTestUtils.makeDynamicModuleIntent(moduleManagedUrl, null); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| waitForModuleLoading(); |
| DynamicModuleCoordinator coordinator = |
| getActivity().getComponent().resolveDynamicModuleCoordinator(); |
| ThreadUtils.runOnUiThread(() -> { |
| CustomTabActivity cctActivity = mCustomTabActivityTestRule.getActivity(); |
| View anyView = new View(cctActivity); |
| coordinator.setTopBarContentView(anyView); |
| // Second call will not crash. |
| coordinator.setTopBarContentView(anyView); |
| }); |
| } |
| |
| @Test |
| @SmallTest |
| @EnableFeatures(ChromeFeatureList.CCT_MODULE) |
| public void testSetTopBarContentView_moduleNotProvided_noTopBar() throws Exception { |
| Intent intent = CustomTabsTestUtils.createMinimalCustomTabIntent( |
| InstrumentationRegistry.getTargetContext(), |
| "https://www.google.com/search?q=london"); |
| intent.putExtra( |
| CustomTabIntentDataProvider.EXTRA_MODULE_MANAGED_HOST_LIST, "www.google.com"); |
| intent.putExtra(CustomTabIntentDataProvider.EXTRA_MODULE_MANAGED_URLS_REGEX, "/search.*"); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| waitForModuleLoading(); |
| |
| ThreadUtils.runOnUiThread(() -> { |
| CustomTabActivity cctActivity = mCustomTabActivityTestRule.getActivity(); |
| View anyView = new View(cctActivity); |
| ViewGroup topBar = cctActivity.findViewById(R.id.topbar); |
| Assert.assertNull(topBar); |
| }); |
| } |
| |
| @Test |
| @SmallTest |
| @DisableFeatures(ChromeFeatureList.CCT_MODULE) |
| public void testSetTopBarContentView_featureDisabled_noTopBar() throws Exception { |
| setupHttpsTestServerAndPages(); |
| String relativeUrl = "/chrome/test/data/android/about.html"; |
| String moduleManagedUrl = mTestServer.getURL(relativeUrl); |
| Intent intent = CustomTabsDynamicModuleTestUtils.makeDynamicModuleIntent( |
| moduleManagedUrl, "^(" + relativeUrl + ")$"); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| waitForModuleLoading(); |
| |
| ThreadUtils.runOnUiThread(() -> { |
| CustomTabActivity cctActivity = mCustomTabActivityTestRule.getActivity(); |
| View anyView = new View(cctActivity); |
| ViewGroup topBar = cctActivity.findViewById(R.id.topbar); |
| Assert.assertNull(topBar); |
| }); |
| } |
| |
| @Test |
| @SmallTest |
| @EnableFeatures(ChromeFeatureList.CCT_MODULE) |
| public void testSetTopBarContentView_moduleLoadingFailed_noTopBar() throws Exception { |
| // Make an intent with nonexistent class name so module loading fail |
| Intent intent = CustomTabsTestUtils.createMinimalCustomTabIntent( |
| InstrumentationRegistry.getTargetContext(), |
| "https://www.google.com/search?q=london"); |
| intent.putExtra(CustomTabIntentDataProvider.EXTRA_MODULE_PACKAGE_NAME, |
| CustomTabsDynamicModuleTestUtils.FAKE_MODULE_PACKAGE_NAME); |
| intent.putExtra(CustomTabIntentDataProvider.EXTRA_MODULE_CLASS_NAME, "ClassName"); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| waitForModuleLoading(); |
| |
| ThreadUtils.runOnUiThread(() -> { |
| CustomTabActivity cctActivity = mCustomTabActivityTestRule.getActivity(); |
| View anyView = new View(cctActivity); |
| cctActivity.getComponent().resolveDynamicModuleCoordinator() |
| .setTopBarContentView(anyView); |
| ViewGroup topBar = cctActivity.findViewById(R.id.topbar); |
| Assert.assertNull(topBar); |
| }); |
| } |
| |
| @Test |
| @SmallTest |
| @EnableFeatures(ChromeFeatureList.CCT_MODULE) |
| public void testSetTopBarContentView_withModuleAndManagedUrls_topBarVisible() throws Exception { |
| setupHttpsTestServerAndPages(); |
| String relativeUrl = "/chrome/test/data/android/about.html"; |
| String moduleManagedUrl = mTestServer.getURL(relativeUrl); |
| Intent intent = CustomTabsDynamicModuleTestUtils.makeDynamicModuleIntent( |
| moduleManagedUrl, "^(" + relativeUrl + ")$"); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| waitForModuleLoading(); |
| |
| ThreadUtils.runOnUiThread(() -> { |
| CustomTabActivity cctActivity = mCustomTabActivityTestRule.getActivity(); |
| View anyView = new View(cctActivity); |
| cctActivity.getComponent().resolveDynamicModuleCoordinator() |
| .setTopBarContentView(anyView); |
| ViewGroup topBar = cctActivity.findViewById(R.id.topbar); |
| Assert.assertNotNull(topBar); |
| Assert.assertThat(anyView.getParent(), equalTo(topBar)); |
| Assert.assertEquals(View.VISIBLE, anyView.getVisibility()); |
| }); |
| } |
| |
| @Test |
| @SmallTest |
| @EnableFeatures({ChromeFeatureList.CCT_MODULE, ChromeFeatureList.CCT_MODULE_CUSTOM_HEADER}) |
| public void testSetTopBarContentView_moduleNotProvided_cctHeaderVisible() throws Exception { |
| Intent intent = CustomTabsTestUtils.createMinimalCustomTabIntent( |
| InstrumentationRegistry.getTargetContext(), |
| "https://www.google.com/search?q=london"); |
| intent.putExtra( |
| CustomTabIntentDataProvider.EXTRA_MODULE_MANAGED_HOST_LIST, "www.google.com"); |
| intent.putExtra(CustomTabIntentDataProvider.EXTRA_MODULE_MANAGED_URLS_REGEX, "/search.*"); |
| intent.putExtra( |
| CustomTabIntentDataProvider.EXTRA_HIDE_CCT_HEADER_ON_MODULE_MANAGED_URLS, true); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| waitForModuleLoading(); |
| |
| ThreadUtils.runOnUiThread(() -> { |
| CustomTabActivity cctActivity = mCustomTabActivityTestRule.getActivity(); |
| View toolbarView = cctActivity.findViewById(R.id.toolbar); |
| Assert.assertTrue( |
| "A custom tab toolbar is never shown", toolbarView instanceof CustomTabToolbar); |
| CustomTabToolbar toolbar = (CustomTabToolbar) toolbarView; |
| Assert.assertEquals(View.VISIBLE, toolbar.getVisibility()); |
| }); |
| } |
| |
| @Test |
| @SmallTest |
| @DisableFeatures(ChromeFeatureList.CCT_MODULE_CUSTOM_HEADER) |
| public void testSetTopBarContentView_featureDisabled_cctHeaderVisible() throws Exception { |
| setupHttpsTestServerAndPages(); |
| String relativeUrl = "/chrome/test/data/android/about.html"; |
| String moduleManagedUrl = mTestServer.getURL(relativeUrl); |
| Intent intent = CustomTabsDynamicModuleTestUtils.makeDynamicModuleIntent( |
| moduleManagedUrl, "^(" + relativeUrl + ")$"); |
| intent.putExtra( |
| CustomTabIntentDataProvider.EXTRA_HIDE_CCT_HEADER_ON_MODULE_MANAGED_URLS, true); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| waitForModuleLoading(); |
| |
| ThreadUtils.runOnUiThread(() -> { |
| CustomTabActivity cctActivity = mCustomTabActivityTestRule.getActivity(); |
| cctActivity.getComponent().resolveDynamicModuleCoordinator() |
| .setTopBarContentView(new View(cctActivity)); |
| View toolbarView = cctActivity.findViewById(R.id.toolbar); |
| Assert.assertTrue( |
| "A custom tab toolbar is never shown", toolbarView instanceof CustomTabToolbar); |
| CustomTabToolbar toolbar = (CustomTabToolbar) toolbarView; |
| Assert.assertEquals(View.VISIBLE, toolbar.getVisibility()); |
| }); |
| } |
| |
| @Test |
| @SmallTest |
| @EnableFeatures({ChromeFeatureList.CCT_MODULE, ChromeFeatureList.CCT_MODULE_CUSTOM_HEADER}) |
| public void testSetTopBarContentView_notModuleManagedHost_cctHeaderVisible() throws Exception { |
| setupHttpsTestServerAndPages(); |
| String relativeUrl = "/chrome/test/data/android/about.html"; |
| String moduleManagedUrl = mTestServer.getURL(relativeUrl); |
| Intent intent = CustomTabsDynamicModuleTestUtils.makeDynamicModuleIntent( |
| moduleManagedUrl, "^(" + relativeUrl + ")$"); |
| intent.putExtra( |
| CustomTabIntentDataProvider.EXTRA_HIDE_CCT_HEADER_ON_MODULE_MANAGED_URLS, true); |
| intent.putExtra( |
| CustomTabIntentDataProvider.EXTRA_MODULE_MANAGED_HOST_LIST, "www.google.com"); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| waitForModuleLoading(); |
| ThreadUtils.runOnUiThread(() -> { |
| CustomTabActivity cctActivity = mCustomTabActivityTestRule.getActivity(); |
| cctActivity.getComponent().resolveDynamicModuleCoordinator() |
| .setTopBarContentView(new View(cctActivity)); |
| View toolbarView = cctActivity.findViewById(R.id.toolbar); |
| Assert.assertTrue( |
| "A custom tab toolbar is never shown", toolbarView instanceof CustomTabToolbar); |
| CustomTabToolbar toolbar = (CustomTabToolbar) toolbarView; |
| Assert.assertEquals(View.VISIBLE, toolbar.getVisibility()); |
| }); |
| } |
| |
| @Test |
| @SmallTest |
| @EnableFeatures({ChromeFeatureList.CCT_MODULE, ChromeFeatureList.CCT_MODULE_CUSTOM_HEADER}) |
| public void testSetTopBarContentView_withModuleAndExtras_cctHeaderHidden() throws Exception { |
| setupHttpsTestServerAndPages(); |
| String relativeUrl = "/chrome/test/data/android/about.html"; |
| String moduleManagedUrl = mTestServer.getURL(relativeUrl); |
| Intent intent = CustomTabsDynamicModuleTestUtils.makeDynamicModuleIntent( |
| moduleManagedUrl, "^(" + relativeUrl + ")$"); |
| intent.putExtra( |
| CustomTabIntentDataProvider.EXTRA_HIDE_CCT_HEADER_ON_MODULE_MANAGED_URLS, true); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| waitForModuleLoading(); |
| |
| ThreadUtils.runOnUiThread(() -> { |
| CustomTabActivity cctActivity = mCustomTabActivityTestRule.getActivity(); |
| cctActivity.getComponent().resolveDynamicModuleCoordinator() |
| .setTopBarContentView(new View(cctActivity)); |
| ViewGroup toolbarContainerView = cctActivity.findViewById(R.id.toolbar_container); |
| for (int index = 0; index < toolbarContainerView.getChildCount(); index++) { |
| View childView = toolbarContainerView.getChildAt(index); |
| if (childView.getId() != R.id.topbar) { |
| Assert.assertEquals(View.GONE, childView.getVisibility()); |
| } |
| } |
| }); |
| } |
| |
| @Test |
| @SmallTest |
| @DisableFeatures(ChromeFeatureList.CCT_MODULE_CUSTOM_HEADER) |
| public void testSetTopBarHeight_featureDisabled_heightNotChanged() throws Exception { |
| setupHttpsTestServerAndPages(); |
| String relativeUrl = "/chrome/test/data/android/about.html"; |
| String moduleManagedUrl = mTestServer.getURL(relativeUrl); |
| Intent intent = CustomTabsDynamicModuleTestUtils.makeDynamicModuleIntent( |
| moduleManagedUrl, "^(" + relativeUrl + ")$"); |
| intent.putExtra( |
| CustomTabIntentDataProvider.EXTRA_HIDE_CCT_HEADER_ON_MODULE_MANAGED_URLS, true); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| |
| ThreadUtils.runOnUiThread(() -> { |
| CustomTabActivity cctActivity = mCustomTabActivityTestRule.getActivity(); |
| int defaultHeight = cctActivity.getFullscreenManager().getTopControlsHeight(); |
| Assert.assertEquals( |
| defaultHeight, cctActivity.getFullscreenManager().getTopControlsHeight()); |
| }); |
| } |
| |
| @Test |
| @SmallTest |
| @EnableFeatures({ChromeFeatureList.CCT_MODULE, ChromeFeatureList.CCT_MODULE_CUSTOM_HEADER}) |
| public void testSetTopBarHeight_cctHeaderNotHidden_heightNotChanged() throws Exception { |
| setupHttpsTestServerAndPages(); |
| String relativeUrl = "/chrome/test/data/android/about.html"; |
| String moduleManagedUrl = mTestServer.getURL(relativeUrl); |
| Intent intent = CustomTabsDynamicModuleTestUtils.makeDynamicModuleIntent( |
| moduleManagedUrl, "^(" + relativeUrl + ")$"); |
| intent.putExtra( |
| CustomTabIntentDataProvider.EXTRA_HIDE_CCT_HEADER_ON_MODULE_MANAGED_URLS, false); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| |
| ThreadUtils.runOnUiThread(() -> { |
| CustomTabActivity cctActivity = mCustomTabActivityTestRule.getActivity(); |
| int defaultHeight = cctActivity.getFullscreenManager().getTopControlsHeight(); |
| int newHeight = defaultHeight + 10; |
| cctActivity.getComponent().resolveDynamicModuleCoordinator().setTopBarHeight(newHeight); |
| Assert.assertEquals( |
| defaultHeight, cctActivity.getFullscreenManager().getTopControlsHeight()); |
| }); |
| } |
| |
| @Test |
| @SmallTest |
| @EnableFeatures({ChromeFeatureList.CCT_MODULE, ChromeFeatureList.CCT_MODULE_CUSTOM_HEADER}) |
| public void testSetTopBarHeight_withModuleAndExtras_heightUpdated() throws Exception { |
| setupHttpsTestServerAndPages(); |
| String relativeUrl = "/chrome/test/data/android/about.html"; |
| String moduleManagedUrl = mTestServer.getURL(relativeUrl); |
| Intent intent = CustomTabsDynamicModuleTestUtils.makeDynamicModuleIntent( |
| moduleManagedUrl, "^(" + relativeUrl + ")$"); |
| intent.putExtra( |
| CustomTabIntentDataProvider.EXTRA_HIDE_CCT_HEADER_ON_MODULE_MANAGED_URLS, true); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| |
| ThreadUtils.runOnUiThread(() -> { |
| CustomTabActivity cctActivity = mCustomTabActivityTestRule.getActivity(); |
| int defaultHeight = cctActivity.getFullscreenManager().getTopControlsHeight(); |
| int newHeight = defaultHeight + 10; |
| cctActivity.getComponent().resolveDynamicModuleCoordinator().setTopBarHeight(newHeight); |
| Assert.assertEquals( |
| newHeight, cctActivity.getFullscreenManager().getTopControlsHeight()); |
| }); |
| } |
| |
| @Test |
| @SmallTest |
| @Feature({"UiCatalogue"}) |
| public void testRemoteViews() throws Exception { |
| Intent intent = createMinimalCustomTabIntent(); |
| |
| Bitmap expectedIcon = createVectorDrawableBitmap(R.drawable.ic_credit_card_black, 77, 48); |
| PendingIntent pi = addActionButtonToIntent(intent, expectedIcon, "Good test"); |
| |
| // Create a RemoteViews. The layout used here is pretty much arbitrary, but with the |
| // constraint that a) it already exists in production code, and b) it only contains views |
| // with the @RemoteView annotation. |
| RemoteViews remoteViews = |
| new RemoteViews(InstrumentationRegistry.getTargetContext().getPackageName(), |
| R.layout.web_notification); |
| remoteViews.setTextViewText(R.id.title, "Kittens!"); |
| remoteViews.setTextViewText(R.id.body, "So fluffy"); |
| remoteViews.setImageViewResource(R.id.icon, R.drawable.ic_music_note_36dp); |
| intent.putExtra(CustomTabsIntent.EXTRA_REMOTEVIEWS, remoteViews); |
| intent.putExtra(CustomTabsIntent.EXTRA_REMOTEVIEWS_VIEW_IDS, new int[] {R.id.icon}); |
| PendingIntent pi2 = PendingIntent.getBroadcast( |
| InstrumentationRegistry.getTargetContext(), 0, new Intent(), 0); |
| intent.putExtra(CustomTabsIntent.EXTRA_REMOTEVIEWS_PENDINGINTENT, pi2); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| mScreenShooter.shoot("Remote Views"); |
| } |
| |
| @Test |
| @SmallTest |
| @RetryOnFailure |
| public void testLaunchWithSession() throws Exception { |
| CustomTabsSessionToken session = warmUpAndLaunchUrlWithSession(); |
| Assert.assertEquals(getActivity().getIntentDataProvider().getSession(), session); |
| } |
| |
| @Test |
| @SmallTest |
| @RetryOnFailure |
| public void testLoadNewUrlWithSession() throws Exception { |
| final Context context = InstrumentationRegistry.getTargetContext(); |
| final Intent intent = CustomTabsTestUtils.createMinimalCustomTabIntent(context, mTestPage); |
| CustomTabsSessionToken session = CustomTabsSessionToken.getSessionTokenFromIntent(intent); |
| warmUpAndLaunchUrlWithSession(intent); |
| Assert.assertEquals(getActivity().getIntentDataProvider().getSession(), session); |
| Assert.assertFalse("CustomTabContentHandler handled intent with wrong session", |
| ThreadUtils.runOnUiThreadBlockingNoException(() -> { |
| return BrowserSessionContentUtils.handleBrowserServicesIntent( |
| CustomTabsTestUtils.createMinimalCustomTabIntent(context, mTestPage2)); |
| })); |
| CriteriaHelper.pollInstrumentationThread( |
| Criteria.equals(mTestPage, () -> getActivity().getActivityTab().getUrl())); |
| Assert.assertTrue("CustomTabContentHandler can't handle intent with same session", |
| ThreadUtils.runOnUiThreadBlockingNoException(() -> { |
| intent.setData(Uri.parse(mTestPage2)); |
| return BrowserSessionContentUtils.handleBrowserServicesIntent(intent); |
| })); |
| final Tab tab = getActivity().getActivityTab(); |
| final CallbackHelper pageLoadFinishedHelper = new CallbackHelper(); |
| tab.addObserver(new EmptyTabObserver() { |
| @Override |
| public void onPageLoadFinished(Tab tab, String url) { |
| pageLoadFinishedHelper.notifyCalled(); |
| } |
| }); |
| pageLoadFinishedHelper.waitForCallback(0); |
| CriteriaHelper.pollInstrumentationThread( |
| Criteria.equals(mTestPage2, () -> getActivity().getActivityTab().getUrl())); |
| } |
| |
| @Test |
| @SmallTest |
| @RetryOnFailure |
| public void testCreateNewTab() throws Exception { |
| final String testUrl = mTestServer.getURL( |
| "/chrome/test/data/android/customtabs/test_window_open.html"); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent( |
| CustomTabsTestUtils.createMinimalCustomTabIntent( |
| InstrumentationRegistry.getTargetContext(), testUrl)); |
| final TabModelSelector tabSelector = |
| mCustomTabActivityTestRule.getActivity().getTabModelSelector(); |
| |
| final CallbackHelper openTabHelper = new CallbackHelper(); |
| ThreadUtils.runOnUiThreadBlocking(() -> { |
| tabSelector.getModel(false).addObserver(new EmptyTabModelObserver() { |
| @Override |
| public void didAddTab(Tab tab, @TabLaunchType int type) { |
| openTabHelper.notifyCalled(); |
| } |
| }); |
| }); |
| DOMUtils.clickNode(mCustomTabActivityTestRule.getWebContents(), "new_window"); |
| |
| openTabHelper.waitForCallback(0, 1); |
| Assert.assertEquals( |
| "A new tab should have been created.", 2, tabSelector.getModel(false).getCount()); |
| } |
| |
| @Test |
| @SmallTest |
| @RetryOnFailure |
| public void testReferrerAddedAutomatically() throws Exception { |
| final Context context = InstrumentationRegistry.getInstrumentation() |
| .getTargetContext() |
| .getApplicationContext(); |
| final Intent intent = CustomTabsTestUtils.createMinimalCustomTabIntent(context, mTestPage2); |
| final CustomTabsSessionToken session = warmUpAndLaunchUrlWithSession(intent); |
| Assert.assertEquals(getActivity().getIntentDataProvider().getSession(), session); |
| CustomTabsConnection connection = CustomTabsConnection.getInstance(); |
| String packageName = context.getPackageName(); |
| final String referrer = |
| IntentHandler.constructValidReferrerForAuthority(packageName).getUrl(); |
| Assert.assertEquals(referrer, connection.getReferrerForSession(session).getUrl()); |
| |
| final Tab tab = getActivity().getActivityTab(); |
| final CallbackHelper pageLoadFinishedHelper = new CallbackHelper(); |
| tab.addObserver(new EmptyTabObserver() { |
| @Override |
| public void onLoadUrl(Tab tab, LoadUrlParams params, int loadType) { |
| Assert.assertEquals(referrer, params.getReferrer().getUrl()); |
| } |
| |
| @Override |
| public void onPageLoadFinished(Tab tab, String url) { |
| pageLoadFinishedHelper.notifyCalled(); |
| } |
| }); |
| Assert.assertTrue("CustomTabContentHandler can't handle intent with same session", |
| ThreadUtils.runOnUiThreadBlockingNoException( |
| () -> BrowserSessionContentUtils.handleBrowserServicesIntent(intent))); |
| pageLoadFinishedHelper.waitForCallback(0); |
| } |
| |
| @Test |
| @SmallTest |
| @RetryOnFailure |
| public void testVerifiedReferrer() throws Exception { |
| final Context context = InstrumentationRegistry.getInstrumentation() |
| .getTargetContext() |
| .getApplicationContext(); |
| final Intent intent = CustomTabsTestUtils.createMinimalCustomTabIntent(context, mTestPage2); |
| String referrer = "https://example.com"; |
| intent.putExtra(Intent.EXTRA_REFERRER_NAME, referrer); |
| CustomTabsSessionToken token = CustomTabsSessionToken.getSessionTokenFromIntent(intent); |
| CustomTabsConnection connection = CustomTabsConnection.getInstance(); |
| connection.newSession(token); |
| connection.overridePackageNameForSessionForTesting(token, "app1"); |
| ThreadUtils.runOnUiThreadBlocking( |
| () -> OriginVerifier.addVerifiedOriginForPackage("app1", new Origin(referrer), |
| CustomTabsService.RELATION_USE_AS_ORIGIN)); |
| |
| final CustomTabsSessionToken session = warmUpAndLaunchUrlWithSession(intent); |
| Assert.assertEquals(getActivity().getIntentDataProvider().getSession(), session); |
| |
| final Tab tab = getActivity().getActivityTab(); |
| final CallbackHelper pageLoadFinishedHelper = new CallbackHelper(); |
| tab.addObserver(new EmptyTabObserver() { |
| @Override |
| public void onLoadUrl(Tab tab, LoadUrlParams params, int loadType) { |
| Assert.assertEquals(referrer, params.getReferrer().getUrl()); |
| } |
| |
| @Override |
| public void onPageLoadFinished(Tab tab, String url) { |
| pageLoadFinishedHelper.notifyCalled(); |
| } |
| }); |
| Assert.assertTrue("CustomTabContentHandler can't handle intent with same session", |
| ThreadUtils.runOnUiThreadBlockingNoException( |
| () -> BrowserSessionContentUtils.handleBrowserServicesIntent(intent))); |
| pageLoadFinishedHelper.waitForCallback(0); |
| } |
| |
| /** |
| * Tests that the navigation callbacks are sent. |
| */ |
| @Test |
| @SmallTest |
| public void testCallbacksAreSent() throws Exception { |
| final Semaphore navigationStartSemaphore = new Semaphore(0); |
| final Semaphore navigationFinishedSemaphore = new Semaphore(0); |
| CustomTabsSession session = CustomTabsTestUtils.bindWithCallback(new CustomTabsCallback() { |
| @Override |
| public void onNavigationEvent(int navigationEvent, Bundle extras) { |
| Assert.assertNotEquals(CustomTabsCallback.NAVIGATION_FAILED, navigationEvent); |
| Assert.assertNotEquals(CustomTabsCallback.NAVIGATION_ABORTED, navigationEvent); |
| if (navigationEvent == CustomTabsCallback.NAVIGATION_STARTED) { |
| navigationStartSemaphore.release(); |
| } else if (navigationEvent == CustomTabsCallback.NAVIGATION_FINISHED) { |
| navigationFinishedSemaphore.release(); |
| } |
| } |
| }); |
| Intent intent = new CustomTabsIntent.Builder(session).build().intent; |
| intent.setData(Uri.parse(mTestPage)); |
| intent.setComponent(new ComponentName( |
| InstrumentationRegistry.getTargetContext(), ChromeLauncherActivity.class)); |
| intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| Assert.assertTrue( |
| navigationStartSemaphore.tryAcquire(TIMEOUT_PAGE_LOAD_SECONDS, TimeUnit.SECONDS)); |
| Assert.assertTrue(navigationFinishedSemaphore.tryAcquire( |
| TIMEOUT_PAGE_LOAD_SECONDS, TimeUnit.SECONDS)); |
| } |
| |
| /** |
| * Tests that page load metrice are sent. |
| */ |
| @Test |
| @SmallTest |
| @RetryOnFailure |
| public void testPageLoadMetricsAreSent() throws Exception { |
| checkPageLoadMetrics(true); |
| } |
| |
| /** |
| * Tests that page load metrics are not sent when the client is not whitelisted. |
| */ |
| @Test |
| @SmallTest |
| public void testPageLoadMetricsAreNotSentByDefault() throws Exception { |
| checkPageLoadMetrics(false); |
| } |
| |
| private static void assertSuffixedHistogramTotalCount(long expected, String histogramPrefix) { |
| for (String suffix : new String[] {".ZoomedIn", ".ZoomedOut"}) { |
| Assert.assertEquals(expected, |
| RecordHistogram.getHistogramTotalCountForTesting(histogramPrefix + suffix)); |
| } |
| } |
| |
| /** |
| * Tests that one navigation in a custom tab records the histograms reflecting time from |
| * intent to first navigation start/commit. |
| */ |
| @Test |
| @SmallTest |
| public void testNavigationHistogramsRecorded() throws Exception { |
| String startHistogramPrefix = "CustomTabs.IntentToFirstNavigationStartTime"; |
| String commitHistogramPrefix = "CustomTabs.IntentToFirstCommitNavigationTime3"; |
| assertSuffixedHistogramTotalCount(0, startHistogramPrefix); |
| assertSuffixedHistogramTotalCount(0, commitHistogramPrefix); |
| |
| final Semaphore semaphore = new Semaphore(0); |
| CustomTabsSession session = CustomTabsTestUtils.bindWithCallback(new CustomTabsCallback() { |
| @Override |
| public void onNavigationEvent(int navigationEvent, Bundle extras) { |
| if (navigationEvent == CustomTabsCallback.NAVIGATION_FINISHED) semaphore.release(); |
| } |
| }); |
| Intent intent = new CustomTabsIntent.Builder(session).build().intent; |
| intent.setData(Uri.parse(mTestPage)); |
| intent.setComponent(new ComponentName( |
| InstrumentationRegistry.getTargetContext(), ChromeLauncherActivity.class)); |
| intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| Assert.assertTrue(semaphore.tryAcquire(TIMEOUT_PAGE_LOAD_SECONDS, TimeUnit.SECONDS)); |
| |
| assertSuffixedHistogramTotalCount(1, startHistogramPrefix); |
| assertSuffixedHistogramTotalCount(1, commitHistogramPrefix); |
| } |
| |
| /** |
| * Tests that TITLE_ONLY state works as expected with a title getting set onload. |
| */ |
| @Test |
| @SmallTest |
| public void testToolbarTitleOnlyStateWithProperTitle() throws Exception { |
| final String url = mWebServer.setResponse("/test.html", ONLOAD_TITLE_CHANGE, null); |
| hideDomainAndEnsureTitleIsSet(url, false, "nytimes.com"); |
| } |
| |
| /** |
| * Tests that TITLE_ONLY state works as expected with a title getting set during prerendering. |
| |
| */ |
| @Test |
| @SmallTest |
| public void testToolbarTitleOnlyStateWithProperTitleHiddenTab() throws Exception { |
| final String url = mWebServer.setResponse("/test.html", ONLOAD_TITLE_CHANGE, null); |
| hideDomainAndEnsureTitleIsSet(url, true, "nytimes.com"); |
| } |
| |
| /** |
| * Tests that TITLE_ONLY state works as expected with a title getting set delayed after load. |
| |
| */ |
| @Test |
| @SmallTest |
| public void testToolbarTitleOnlyStateWithDelayedTitle() throws Exception { |
| final String url = mWebServer.setResponse("/test.html", DELAYED_TITLE_CHANGE, null); |
| hideDomainAndEnsureTitleIsSet(url, false, "nytimes.com"); |
| } |
| |
| private void hideDomainAndEnsureTitleIsSet( |
| final String url, boolean useHiddenTab, final String expectedTitle) throws Exception { |
| final CustomTabsConnection connection = CustomTabsTestUtils.warmUpAndWait(); |
| Context context = InstrumentationRegistry.getTargetContext(); |
| Intent intent = CustomTabsTestUtils.createMinimalCustomTabIntent(context, url); |
| intent.putExtra( |
| CustomTabsIntent.EXTRA_TITLE_VISIBILITY_STATE, CustomTabsIntent.SHOW_PAGE_TITLE); |
| final CustomTabsSessionToken token = |
| CustomTabsSessionToken.getSessionTokenFromIntent(intent); |
| Assert.assertTrue(connection.newSession(token)); |
| connection.mClientManager.setHideDomainForSession(token, true); |
| |
| if (useHiddenTab) { |
| connection.setCanUseHiddenTabForSession(token, true); |
| Assert.assertTrue(connection.mayLaunchUrl(token, Uri.parse(url), null, null)); |
| ensureCompletedSpeculationForUrl(connection, url); |
| } |
| |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| CriteriaHelper.pollUiThread(new Criteria() { |
| @Override |
| public boolean isSatisfied() { |
| final Tab currentTab = mCustomTabActivityTestRule.getActivity().getActivityTab(); |
| return url.equals(currentTab.getUrl()); |
| } |
| }); |
| CriteriaHelper.pollUiThread(new Criteria() { |
| @Override |
| public boolean isSatisfied() { |
| CustomTabToolbar toolbar = |
| mCustomTabActivityTestRule.getActivity().findViewById(R.id.toolbar); |
| TextView titleBar = toolbar.findViewById(R.id.title_bar); |
| return titleBar != null && titleBar.isShown() |
| && (titleBar.getText()).toString().equals(expectedTitle); |
| } |
| }); |
| } |
| |
| /** |
| * Tests that basic postMessage functionality works through sending a single postMessage |
| * request. |
| */ |
| @Test |
| @SmallTest |
| @RetryOnFailure |
| public void testPostMessageBasic() throws Exception { |
| final CustomTabsConnection connection = CustomTabsTestUtils.warmUpAndWait(); |
| Context context = InstrumentationRegistry.getTargetContext(); |
| Intent intent = CustomTabsTestUtils.createMinimalCustomTabIntent(context, mTestPage); |
| final CustomTabsSessionToken token = |
| CustomTabsSessionToken.getSessionTokenFromIntent(intent); |
| Assert.assertTrue(connection.newSession(token)); |
| Assert.assertTrue(connection.requestPostMessageChannel(token, null)); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| CriteriaHelper.pollInstrumentationThread(new Criteria() { |
| @Override |
| public boolean isSatisfied() { |
| final Tab currentTab = mCustomTabActivityTestRule.getActivity().getActivityTab(); |
| return mTestPage.equals(currentTab.getUrl()); |
| } |
| }); |
| Assert.assertTrue( |
| connection.postMessage(token, "Message", null) == CustomTabsService.RESULT_SUCCESS); |
| ThreadUtils.runOnUiThreadBlocking( |
| (Runnable) () |
| -> mCustomTabActivityTestRule.getActivity().getActivityTab().loadUrl( |
| new LoadUrlParams(mTestPage2))); |
| CriteriaHelper.pollUiThread(new Criteria() { |
| @Override |
| public boolean isSatisfied() { |
| final Tab currentTab = mCustomTabActivityTestRule.getActivity().getActivityTab(); |
| return currentTab.isLoadingAndRenderingDone(); |
| } |
| }); |
| Assert.assertTrue(connection.postMessage(token, "Message", null) |
| == CustomTabsService.RESULT_FAILURE_MESSAGING_ERROR); |
| } |
| |
| /** |
| * Tests that postMessage channel is not functioning after web contents get destroyed and also |
| * not breaking things. |
| */ |
| @Test |
| @SmallTest |
| @RetryOnFailure |
| public void testPostMessageWebContentsDestroyed() throws Exception { |
| final CustomTabsConnection connection = CustomTabsTestUtils.warmUpAndWait(); |
| Context context = InstrumentationRegistry.getTargetContext(); |
| Intent intent = CustomTabsTestUtils.createMinimalCustomTabIntent(context, mTestPage); |
| final CustomTabsSessionToken token = |
| CustomTabsSessionToken.getSessionTokenFromIntent(intent); |
| Assert.assertTrue(connection.newSession(token)); |
| Assert.assertTrue(connection.requestPostMessageChannel(token, null)); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| CriteriaHelper.pollInstrumentationThread(new Criteria() { |
| @Override |
| public boolean isSatisfied() { |
| final Tab currentTab = mCustomTabActivityTestRule.getActivity().getActivityTab(); |
| return mTestPage.equals(currentTab.getUrl()); |
| } |
| }); |
| Assert.assertTrue( |
| connection.postMessage(token, "Message", null) == CustomTabsService.RESULT_SUCCESS); |
| |
| final CallbackHelper renderProcessCallback = new CallbackHelper(); |
| new WebContentsObserver(mCustomTabActivityTestRule.getWebContents()) { |
| @Override |
| public void renderProcessGone(boolean wasOomProtected) { |
| renderProcessCallback.notifyCalled(); |
| } |
| }; |
| ThreadUtils.postOnUiThread(() -> { |
| WebContentsUtils.simulateRendererKilled( |
| mCustomTabActivityTestRule.getActivity().getActivityTab().getWebContents(), |
| false); |
| }); |
| renderProcessCallback.waitForCallback(0); |
| Assert.assertTrue(connection.postMessage(token, "Message", null) |
| == CustomTabsService.RESULT_FAILURE_MESSAGING_ERROR); |
| } |
| |
| /** |
| * Tests whether validatePostMessageOrigin is necessary for making successful postMessage |
| * requests. |
| */ |
| @Test |
| @SmallTest |
| @RetryOnFailure |
| public void testPostMessageRequiresValidation() throws Exception { |
| final CustomTabsConnection connection = CustomTabsTestUtils.warmUpAndWait(); |
| Context context = InstrumentationRegistry.getTargetContext(); |
| Intent intent = CustomTabsTestUtils.createMinimalCustomTabIntent(context, mTestPage); |
| final CustomTabsSessionToken token = |
| CustomTabsSessionToken.getSessionTokenFromIntent(intent); |
| Assert.assertTrue(connection.newSession(token)); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| CriteriaHelper.pollInstrumentationThread(new Criteria() { |
| @Override |
| public boolean isSatisfied() { |
| final Tab currentTab = mCustomTabActivityTestRule.getActivity().getActivityTab(); |
| return mTestPage.equals(currentTab.getUrl()); |
| } |
| }); |
| Assert.assertTrue(connection.postMessage(token, "Message", null) |
| == CustomTabsService.RESULT_FAILURE_MESSAGING_ERROR); |
| } |
| |
| /** |
| * Tests the sent postMessage requests not only return success, but is also received by page. |
| */ |
| @Test |
| @SmallTest |
| @RetryOnFailure |
| public void testPostMessageReceivedInPage() throws Exception { |
| final String url = |
| mWebServer.setResponse("/test.html", TITLE_FROM_POSTMESSAGE_TO_CHANNEL, null); |
| final CustomTabsConnection connection = CustomTabsTestUtils.warmUpAndWait(); |
| Context context = InstrumentationRegistry.getTargetContext(); |
| Intent intent = CustomTabsTestUtils.createMinimalCustomTabIntent(context, url); |
| final CustomTabsSessionToken token = |
| CustomTabsSessionToken.getSessionTokenFromIntent(intent); |
| Assert.assertTrue(connection.newSession(token)); |
| Assert.assertTrue(connection.requestPostMessageChannel(token, null)); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| CriteriaHelper.pollInstrumentationThread(new Criteria() { |
| @Override |
| public boolean isSatisfied() { |
| final Tab currentTab = mCustomTabActivityTestRule.getActivity().getActivityTab(); |
| return url.equals(currentTab.getUrl()); |
| } |
| }); |
| Assert.assertTrue(connection.postMessage(token, "New title", null) |
| == CustomTabsService.RESULT_SUCCESS); |
| waitForTitle("New title"); |
| } |
| |
| /** |
| * Tests the postMessage requests sent from the page is received on the client side. |
| */ |
| @Test |
| @SmallTest |
| @RetryOnFailure |
| public void testPostMessageReceivedFromPage() 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); |
| CustomTabsTestUtils.warmUpAndWait(); |
| final CustomTabsSession session = |
| CustomTabsTestUtils.bindWithCallback(new CustomTabsCallback() { |
| @Override |
| public void onMessageChannelReady(Bundle extras) { |
| messageChannelHelper.notifyCalled(); |
| } |
| |
| @Override |
| public void onPostMessage(String message, Bundle extras) { |
| onPostMessageHelper.notifyCalled(); |
| } |
| }); |
| session.requestPostMessageChannel(null); |
| Intent intent = new CustomTabsIntent.Builder(session).build().intent; |
| intent.setData(Uri.parse(url)); |
| intent.setComponent(new ComponentName( |
| InstrumentationRegistry.getTargetContext(), ChromeLauncherActivity.class)); |
| intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| |
| Assert.assertTrue(session.postMessage("Message", null) |
| == CustomTabsService.RESULT_FAILURE_MESSAGING_ERROR); |
| |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| messageChannelHelper.waitForCallback(0); |
| onPostMessageHelper.waitForCallback(0); |
| } |
| |
| /** |
| * Tests the postMessage requests sent from the page is received on the client side even though |
| * the request is sent after the page is created. |
| */ |
| @Test |
| @SmallTest |
| @RetryOnFailure |
| public void testPostMessageReceivedFromPageWithLateRequest() 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); |
| CustomTabsTestUtils.warmUpAndWait(); |
| final CustomTabsSession session = |
| CustomTabsTestUtils.bindWithCallback(new CustomTabsCallback() { |
| @Override |
| public void onMessageChannelReady(Bundle extras) { |
| messageChannelHelper.notifyCalled(); |
| } |
| |
| @Override |
| public void onPostMessage(String message, Bundle extras) { |
| onPostMessageHelper.notifyCalled(); |
| } |
| }); |
| |
| Intent intent = new CustomTabsIntent.Builder(session).build().intent; |
| intent.setData(Uri.parse(url)); |
| intent.setComponent(new ComponentName( |
| InstrumentationRegistry.getTargetContext(), ChromeLauncherActivity.class)); |
| intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| CriteriaHelper.pollInstrumentationThread(new Criteria() { |
| @Override |
| public boolean isSatisfied() { |
| final Tab currentTab = mCustomTabActivityTestRule.getActivity().getActivityTab(); |
| return url.equals(currentTab.getUrl()); |
| } |
| }); |
| |
| session.requestPostMessageChannel(null); |
| |
| messageChannelHelper.waitForCallback(0); |
| onPostMessageHelper.waitForCallback(0); |
| |
| Assert.assertTrue(session.postMessage("Message", null) == CustomTabsService.RESULT_SUCCESS); |
| } |
| |
| private static final int BEFORE_MAY_LAUNCH_URL = 0; |
| private static final int BEFORE_INTENT = 1; |
| private static final int AFTER_INTENT = 2; |
| |
| /** |
| * Tests a postMessage request chain can start while loading a hidden tab and continue |
| * afterwards. Request sent before the hidden tab start. |
| */ |
| @Test |
| @SmallTest |
| @RetryOnFailure |
| @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) |
| public void testPostMessageThroughHiddenTabWithRequestBeforeMayLaunchUrl() throws Exception { |
| sendPostMessageDuringHiddenTabTransition(BEFORE_MAY_LAUNCH_URL); |
| } |
| |
| /** |
| * Tests a postMessage request chain can start while loading a hidden tab and continue |
| * afterwards. Request sent after the hidden tab start and before intent launched. |
| */ |
| @Test |
| @SmallTest |
| @RetryOnFailure |
| @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) |
| public void testPostMessageThroughHiddenTabWithRequestBeforeIntent() throws Exception { |
| sendPostMessageDuringHiddenTabTransition(BEFORE_INTENT); |
| } |
| |
| /** |
| * Tests a postMessage request chain can start while loading a hidden tab and continue |
| * afterwards. Request sent after intent received. |
| */ |
| @Test |
| @SmallTest |
| @RetryOnFailure |
| @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) |
| public void testPostMessageThroughHiddenTabWithRequestAfterIntent() throws Exception { |
| sendPostMessageDuringHiddenTabTransition(AFTER_INTENT); |
| } |
| |
| private void sendPostMessageDuringHiddenTabTransition(int requestTime) throws Exception { |
| final CallbackHelper messageChannelHelper = new CallbackHelper(); |
| final String url = |
| mWebServer.setResponse("/test.html", TITLE_FROM_POSTMESSAGE_TO_CHANNEL, null); |
| final CustomTabsConnection connection = CustomTabsTestUtils.warmUpAndWait(); |
| |
| final CustomTabsSession session = |
| CustomTabsTestUtils.bindWithCallback(new CustomTabsCallback() { |
| @Override |
| public void onMessageChannelReady(Bundle extras) { |
| messageChannelHelper.notifyCalled(); |
| } |
| }); |
| |
| Intent intent = new CustomTabsIntent.Builder(session).build().intent; |
| intent.setData(Uri.parse(url)); |
| intent.setComponent(new ComponentName( |
| InstrumentationRegistry.getTargetContext(), ChromeLauncherActivity.class)); |
| intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| CustomTabsSessionToken token = CustomTabsSessionToken.getSessionTokenFromIntent(intent); |
| |
| boolean channelRequested = false; |
| String titleString = ""; |
| |
| if (requestTime == BEFORE_MAY_LAUNCH_URL) { |
| channelRequested = session.requestPostMessageChannel(null); |
| Assert.assertTrue(channelRequested); |
| } |
| |
| connection.setCanUseHiddenTabForSession(token, true); |
| session.mayLaunchUrl(Uri.parse(url), null, null); |
| ensureCompletedSpeculationForUrl(connection, url); |
| |
| if (requestTime == BEFORE_INTENT) { |
| channelRequested = session.requestPostMessageChannel(null); |
| Assert.assertTrue(channelRequested); |
| } |
| |
| if (channelRequested) { |
| messageChannelHelper.waitForCallback(0); |
| String currentMessage = "Prerendering "; |
| // Initial title update during prerender. |
| Assert.assertTrue( |
| session.postMessage(currentMessage, null) == CustomTabsService.RESULT_SUCCESS); |
| titleString = currentMessage; |
| } |
| |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| |
| CriteriaHelper.pollInstrumentationThread(new Criteria() { |
| @Override |
| public boolean isSatisfied() { |
| final Tab currentTab = mCustomTabActivityTestRule.getActivity().getActivityTab(); |
| return url.equals(currentTab.getUrl()); |
| } |
| }); |
| |
| if (requestTime == AFTER_INTENT) { |
| channelRequested = session.requestPostMessageChannel(null); |
| Assert.assertTrue(channelRequested); |
| messageChannelHelper.waitForCallback(0); |
| } |
| |
| String currentMessage = "and loading "; |
| // Update title again and verify both updates went through with the channel still intact. |
| Assert.assertTrue( |
| session.postMessage(currentMessage, null) == CustomTabsService.RESULT_SUCCESS); |
| titleString += currentMessage; |
| |
| // Request a new channel, verify it was created. |
| session.requestPostMessageChannel(null); |
| messageChannelHelper.waitForCallback(1); |
| |
| String newMessage = "and refreshing"; |
| // Update title again and verify both updates went through with the channel still intact. |
| Assert.assertTrue( |
| session.postMessage(newMessage, null) == CustomTabsService.RESULT_SUCCESS); |
| titleString += newMessage; |
| |
| final String title = titleString; |
| 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); |
| |
| Intent intent = CustomTabsDynamicModuleTestUtils.makeDynamicModuleIntent(url, null); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| |
| ChromeTabUtils.waitForTabPageLoaded(getActivity().getActivityTab(), url); |
| DynamicModuleCoordinator coordinator = |
| getActivity().getComponent().resolveDynamicModuleCoordinator(); |
| 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. |
| Assert.assertEquals(coordinator.postMessage("New title"), |
| CustomTabsService.RESULT_SUCCESS); |
| return true; |
| } |
| |
| @Override |
| public void onDisconnectChannel(Context appContext) {} |
| }); |
| |
| Assert.assertTrue(coordinator.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, 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); |
| |
| Intent intent = CustomTabsDynamicModuleTestUtils.makeDynamicModuleIntent(url, null); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| |
| ChromeTabUtils.waitForTabPageLoaded(getActivity().getActivityTab(), url); |
| |
| DynamicModuleCoordinator coordinator = |
| getActivity().getComponent().resolveDynamicModuleCoordinator(); |
| |
| 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) {} |
| }); |
| |
| Assert.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 |
| @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); |
| |
| // If feature disabled dynamic module is not instantiated |
| DynamicModuleCoordinator coordinator = |
| getActivity().getComponent().resolveDynamicModuleCoordinator(); |
| |
| // We shouldn't be able to open a channel or post messages yet. |
| Assert.assertFalse(coordinator |
| .requestPostMessageChannel(FAKE_ORIGIN_URI)); |
| Assert.assertTrue(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. |
| Assert.assertTrue(coordinator |
| .requestPostMessageChannel(FAKE_ORIGIN_URI)); |
| } |
| |
| @Test |
| @SmallTest |
| @EnableFeatures(ChromeFeatureList.CCT_MODULE) |
| @DisableFeatures(ChromeFeatureList.CCT_MODULE_POST_MESSAGE) |
| public void testPostMessageFromDynamicModuleDisallowedWhenFeatureDisabled() throws Exception { |
| final String url = mWebServer.setResponse("/test.html", MESSAGE_FROM_PAGE_TO_CHANNEL, null); |
| |
| Intent intent = CustomTabsDynamicModuleTestUtils.makeDynamicModuleIntent(url, null); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| |
| ChromeTabUtils.waitForTabPageLoaded(getActivity().getActivityTab(), url); |
| DynamicModuleCoordinator coordinator = getActivity().getComponent() |
| .resolveDynamicModuleCoordinator(); |
| // We shouldn't be able to open a channel or post messages yet. |
| Assert.assertFalse(coordinator.requestPostMessageChannel(FAKE_ORIGIN_URI)); |
| Assert.assertEquals(coordinator.postMessage("Message"), |
| CustomTabsService.RESULT_FAILURE_DISALLOWED); |
| } |
| |
| /** |
| * Tests that when we use a pre-created renderer, the page loaded is the |
| * only one in the navigation history. |
| */ |
| @Test |
| @SmallTest |
| @RetryOnFailure |
| public void testPrecreatedRenderer() throws Exception { |
| CustomTabsConnection connection = CustomTabsTestUtils.warmUpAndWait(); |
| Context context = InstrumentationRegistry.getTargetContext(); |
| Intent intent = CustomTabsTestUtils.createMinimalCustomTabIntent(context, mTestPage); |
| CustomTabsSessionToken token = CustomTabsSessionToken.getSessionTokenFromIntent(intent); |
| Assert.assertTrue(connection.newSession(token)); |
| // Forcing no hidden tab implies falling back to simply creating a spare WebContents. |
| connection.setCanUseHiddenTabForSession(token, false); |
| Assert.assertTrue(connection.mayLaunchUrl(token, Uri.parse(mTestPage), null, null)); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| CriteriaHelper.pollInstrumentationThread(new Criteria() { |
| @Override |
| public boolean isSatisfied() { |
| final Tab currentTab = mCustomTabActivityTestRule.getActivity().getActivityTab(); |
| return mTestPage.equals(currentTab.getUrl()); |
| } |
| }); |
| |
| Assert.assertFalse(mCustomTabActivityTestRule.getActivity().getActivityTab().canGoBack()); |
| Assert.assertFalse( |
| mCustomTabActivityTestRule.getActivity().getActivityTab().canGoForward()); |
| |
| List<HistoryItem> history = getHistory(); |
| Assert.assertEquals(1, history.size()); |
| Assert.assertEquals(mTestPage, history.get(0).getUrl()); |
| } |
| |
| /** Tests that calling warmup() is optional without prerendering. */ |
| @Test |
| @SmallTest |
| @RetryOnFailure |
| public void testMayLaunchUrlWithoutWarmupNoSpeculation() throws InterruptedException { |
| mayLaunchUrlWithoutWarmup(false); |
| } |
| |
| /** Tests that calling mayLaunchUrl() without warmup() succeeds. */ |
| @Test |
| @SmallTest |
| @RetryOnFailure |
| public void testMayLaunchUrlWithoutWarmupHiddenTab() throws InterruptedException { |
| mayLaunchUrlWithoutWarmup(true); |
| } |
| |
| /** |
| * Tests that launching a regular Chrome tab after warmup() gives the right layout. |
| * |
| * About the restrictions and switches: No FRE and no document mode to get a |
| * ChromeTabbedActivity, and no tablets to have the tab switcher button. |
| * |
| * Non-regression test for crbug.com/547121. |
| * @SmallTest |
| * Disabled for flake: https://crbug.com/692025. |
| */ |
| @Test |
| @DisabledTest |
| @Restriction(UiRestriction.RESTRICTION_TYPE_PHONE) |
| @CommandLineFlags.Add(ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE) |
| public void testWarmupAndLaunchRegularChrome() throws Exception { |
| CustomTabsTestUtils.warmUpAndWait(); |
| Intent intent = new Intent( |
| InstrumentationRegistry.getTargetContext(), ChromeLauncherActivity.class); |
| intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| |
| Instrumentation.ActivityMonitor monitor = |
| InstrumentationRegistry.getInstrumentation().addMonitor( |
| ChromeTabbedActivity.class.getName(), null, false); |
| Activity activity = InstrumentationRegistry.getInstrumentation().startActivitySync(intent); |
| Assert.assertNotNull("Main activity did not start", activity); |
| ChromeTabbedActivity tabbedActivity = |
| (ChromeTabbedActivity) monitor.waitForActivityWithTimeout( |
| ChromeActivityTestRule.getActivityStartTimeoutMs()); |
| Assert.assertNotNull("ChromeTabbedActivity did not start", tabbedActivity); |
| Assert.assertNotNull("Should have a tab switcher button.", |
| tabbedActivity.findViewById(R.id.tab_switcher_button)); |
| } |
| |
| /** |
| * Tests that launching a Custom Tab after warmup() gives the right layout. |
| * |
| * Non-regression test for crbug.com/547121. |
| */ |
| @Test |
| @SmallTest |
| @Restriction(UiRestriction.RESTRICTION_TYPE_PHONE) |
| @CommandLineFlags.Add(ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE) |
| public void testWarmupAndLaunchRightToolbarLayout() throws Exception { |
| CustomTabsTestUtils.warmUpAndWait(); |
| mCustomTabActivityTestRule.startActivityCompletely(createMinimalCustomTabIntent()); |
| Assert.assertNull("Should not have a tab switcher button.", |
| getActivity().findViewById(R.id.tab_switcher_button)); |
| } |
| |
| /** |
| * Tests the following scenario: |
| * - warmup() + mayLaunchUrl("http://example.com/page.html#first-fragment") |
| * - loadUrl("http://example.com/page.html#other-fragment") |
| * |
| * The expected behavior is that the hidden tab shouldn't be dropped, and that the fragment is |
| * updated. |
| */ |
| @Test |
| @SmallTest |
| @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) |
| @RetryOnFailure |
| public void testHiddenTabAndChangingFragmentIgnoreFragments() throws Exception { |
| startHiddenTabAndChangeFragment(true, true); |
| } |
| |
| /** Same as above, but the hidden tab matching should not ignore the fragment. */ |
| @Test |
| @SmallTest |
| @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) |
| @RetryOnFailure |
| public void testHiddenTabAndChangingFragmentDontIgnoreFragments() throws Exception { |
| startHiddenTabAndChangeFragment(false, true); |
| } |
| |
| /** Same as above, hidden tab matching ignores the fragment, don't wait. */ |
| @Test |
| @SmallTest |
| @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) |
| @RetryOnFailure |
| public void testHiddenTabAndChangingFragmentDontWait() throws Exception { |
| startHiddenTabAndChangeFragment(true, false); |
| } |
| |
| /** Same as above, hidden tab matching doesn't ignore the fragment, don't wait. */ |
| @Test |
| @SmallTest |
| @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) |
| @RetryOnFailure |
| public void testHiddenTabAndChangingFragmentDontWaitDrop() throws Exception { |
| startHiddenTabAndChangeFragment(false, false); |
| } |
| |
| @Test |
| @SmallTest |
| 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: |
| * - warmup() + mayLaunchUrl("http://example.com/page.html#first-fragment") |
| * - loadUrl("http://example.com/page.html#other-fragment") |
| * |
| * There are two parameters changing the bahavior: |
| * @param ignoreFragments Whether the hidden tab should be kept. |
| * @param wait Whether to wait for the hidden tab to load. |
| */ |
| private void startHiddenTabAndChangeFragment(boolean ignoreFragments, boolean wait) |
| throws Exception { |
| String testUrl = mTestServer.getURL(FRAGMENT_TEST_PAGE); |
| String initialFragment = "#test"; |
| String initialUrl = testUrl + initialFragment; |
| String fragment = "#yeah"; |
| String urlWithFragment = testUrl + fragment; |
| |
| Context context = InstrumentationRegistry.getInstrumentation() |
| .getTargetContext() |
| .getApplicationContext(); |
| CustomTabsConnection connection = CustomTabsTestUtils.warmUpAndWait(); |
| Intent intent = CustomTabsTestUtils.createMinimalCustomTabIntent(context, urlWithFragment); |
| CustomTabsSessionToken token = CustomTabsSessionToken.getSessionTokenFromIntent(intent); |
| connection.newSession(token); |
| connection.setIgnoreUrlFragmentsForSession(token, ignoreFragments); |
| connection.setCanUseHiddenTabForSession(token, true); |
| Assert.assertTrue(connection.mayLaunchUrl(token, Uri.parse(initialUrl), null, null)); |
| |
| if (wait) { |
| ensureCompletedSpeculationForUrl(connection, initialUrl); |
| } |
| |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| final Tab tab = getActivity().getActivityTab(); |
| |
| if (wait) { |
| ElementContentCriteria initialVisibilityCriteria = new ElementContentCriteria( |
| tab, "visibility", ignoreFragments ? "hidden" : "visible"); |
| ElementContentCriteria initialFragmentCriteria = new ElementContentCriteria( |
| tab, "initial-fragment", ignoreFragments ? initialFragment : fragment); |
| ElementContentCriteria fragmentCriteria = |
| new ElementContentCriteria(tab, "fragment", fragment); |
| // The tab hasn't been reloaded. |
| CriteriaHelper.pollInstrumentationThread(initialVisibilityCriteria, 2000, 200); |
| // No reload (initial fragment is correct). |
| CriteriaHelper.pollInstrumentationThread(initialFragmentCriteria, 2000, 200); |
| if (ignoreFragments) { |
| CriteriaHelper.pollInstrumentationThread(fragmentCriteria, 2000, 200); |
| } |
| } else { |
| CriteriaHelper.pollInstrumentationThread(new ElementContentCriteria( |
| tab, "initial-fragment", fragment), 2000, 200); |
| } |
| |
| Assert.assertFalse(tab.canGoForward()); |
| Assert.assertFalse(tab.canGoBack()); |
| |
| // TODO(ahemery): |
| // Fragment misses will trigger two history entries |
| // - url#speculated and url#actual are both inserted |
| // This should ideally not be the case. |
| } |
| |
| /** |
| * Test whether the url shown on hidden tab gets updated from about:blank when it |
| * completes in the background. |
| */ |
| @Test |
| @SmallTest |
| @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) |
| @RetryOnFailure |
| public void testHiddenTabCorrectUrl() throws Exception { |
| Context context = InstrumentationRegistry.getInstrumentation() |
| .getTargetContext() |
| .getApplicationContext(); |
| final CustomTabsConnection connection = CustomTabsTestUtils.warmUpAndWait(); |
| CustomTabsSessionToken token = CustomTabsSessionToken.createMockSessionTokenForTesting(); |
| connection.newSession(token); |
| connection.setCanUseHiddenTabForSession(token, true); |
| Assert.assertTrue(connection.mayLaunchUrl(token, Uri.parse(mTestPage), null, null)); |
| ensureCompletedSpeculationForUrl(connection, mTestPage); |
| |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent( |
| CustomTabsTestUtils.createMinimalCustomTabIntent(context, mTestPage)); |
| Assert.assertEquals(Uri.parse(mTestPage).getHost() + ":" + Uri.parse(mTestPage).getPort(), |
| ((EditText) getActivity().findViewById(R.id.url_bar)).getText().toString()); |
| } |
| |
| /** |
| * Test that hidden tab speculation is not performed if 3rd party cookies are blocked. |
| **/ |
| @Test |
| @SmallTest |
| @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) |
| @RetryOnFailure |
| @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE}) |
| public void testHiddenTabThirdPartyCookiesBlocked() throws Exception { |
| final CustomTabsConnection connection = CustomTabsTestUtils.warmUpAndWait(); |
| final CustomTabsSessionToken token = |
| CustomTabsSessionToken.createMockSessionTokenForTesting(); |
| connection.newSession(token); |
| connection.setCanUseHiddenTabForSession(token, true); |
| connection.warmup(0); |
| |
| // Needs the browser process to be initialized. |
| ThreadUtils.runOnUiThreadBlocking(() -> { |
| PrefServiceBridge prefs = PrefServiceBridge.getInstance(); |
| boolean old_block_pref = prefs.isBlockThirdPartyCookiesEnabled(); |
| prefs.setBlockThirdPartyCookiesEnabled(false); |
| Assert.assertTrue(connection.maySpeculate(token)); |
| prefs.setBlockThirdPartyCookiesEnabled(true); |
| Assert.assertFalse(connection.maySpeculate(token)); |
| prefs.setBlockThirdPartyCookiesEnabled(old_block_pref); |
| }); |
| } |
| |
| /** |
| * Test whether invalid urls are avoided for hidden tab. |
| */ |
| @Test |
| @SmallTest |
| @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) |
| @RetryOnFailure |
| public void testHiddenTabInvalidUrl() throws Exception { |
| final CustomTabsConnection connection = CustomTabsTestUtils.warmUpAndWait(); |
| CustomTabsSessionToken token = CustomTabsSessionToken.createMockSessionTokenForTesting(); |
| connection.newSession(token); |
| connection.setCanUseHiddenTabForSession(token, true); |
| Assert.assertFalse( |
| connection.mayLaunchUrl(token, Uri.parse("chrome://version"), null, null)); |
| } |
| |
| /** |
| * Tests that the activity knows there is already a child process when warmup() has been called. |
| */ |
| @Test |
| @SmallTest |
| @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) |
| @RetryOnFailure |
| public void testAllocateChildConnectionWithWarmup() throws Exception { |
| Context context = InstrumentationRegistry.getInstrumentation() |
| .getTargetContext() |
| .getApplicationContext(); |
| final CustomTabsConnection connection = CustomTabsTestUtils.warmUpAndWait(); |
| CustomTabsSessionToken token = CustomTabsSessionToken.createMockSessionTokenForTesting(); |
| connection.newSession(token); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent( |
| CustomTabsTestUtils.createMinimalCustomTabIntent(context, mTestPage)); |
| ThreadUtils.runOnUiThreadBlocking( |
| () -> Assert.assertFalse( |
| "Warmup() should have allocated a child connection", |
| getActivity().shouldAllocateChildConnection())); |
| } |
| |
| /** |
| * Tests that the activity knows there is no child process. |
| */ |
| @Test |
| @SmallTest |
| @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) |
| @RetryOnFailure |
| public void testAllocateChildConnectionNoWarmup() throws Exception { |
| Context context = InstrumentationRegistry.getInstrumentation() |
| .getTargetContext() |
| .getApplicationContext(); |
| final CustomTabsConnection connection = CustomTabsConnection.getInstance(); |
| CustomTabsSessionToken token = CustomTabsSessionToken.createMockSessionTokenForTesting(); |
| connection.newSession(token); |
| |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent( |
| CustomTabsTestUtils.createMinimalCustomTabIntent(context, mTestPage2)); |
| ThreadUtils.runOnUiThreadBlocking( |
| () -> Assert.assertTrue( |
| "No spare renderer available, should allocate a child connection.", |
| getActivity().shouldAllocateChildConnection())); |
| } |
| |
| /** |
| * Tests that the activity knows there is already a child process with a hidden tab. |
| */ |
| @Test |
| @SmallTest |
| @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) |
| @RetryOnFailure |
| public void testAllocateChildConnectionWithHiddenTab() throws Exception { |
| Context context = InstrumentationRegistry.getInstrumentation() |
| .getTargetContext() |
| .getApplicationContext(); |
| final CustomTabsConnection connection = CustomTabsTestUtils.warmUpAndWait(); |
| CustomTabsSessionToken token = CustomTabsSessionToken.createMockSessionTokenForTesting(); |
| connection.newSession(token); |
| connection.setCanUseHiddenTabForSession(token, true); |
| Assert.assertTrue(connection.mayLaunchUrl(token, Uri.parse(mTestPage), null, null)); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent( |
| CustomTabsTestUtils.createMinimalCustomTabIntent(context, mTestPage)); |
| ThreadUtils.runOnUiThreadBlocking( |
| () -> Assert.assertFalse( |
| "Prerendering should have allocated a child connection", |
| getActivity().shouldAllocateChildConnection())); |
| } |
| |
| @Test |
| @SmallTest |
| @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) |
| public void testRecreateSpareRendererOnTabClose() throws Exception { |
| Context context = InstrumentationRegistry.getInstrumentation() |
| .getTargetContext() |
| .getApplicationContext(); |
| CustomTabsTestUtils.warmUpAndWait(); |
| |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent( |
| CustomTabsTestUtils.createMinimalCustomTabIntent(context, mTestPage)); |
| |
| ThreadUtils.runOnUiThreadBlocking(() -> { |
| Assert.assertFalse(WarmupManager.getInstance().hasSpareWebContents()); |
| final CustomTabActivity activity = mCustomTabActivityTestRule.getActivity(); |
| activity.finishAndClose(false); |
| }); |
| CriteriaHelper.pollUiThread(new Criteria("No new spare renderer") { |
| @Override |
| public boolean isSatisfied() { |
| return WarmupManager.getInstance().hasSpareWebContents(); |
| } |
| }, 2000, 200); |
| } |
| |
| /** |
| * Tests that hidden tab accepts a referrer, and that this is not lost when launching the |
| * Custom Tab. |
| */ |
| @Test |
| @SmallTest |
| @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) |
| @RetryOnFailure |
| public void testHiddenTabWithReferrer() throws Exception { |
| String referrer = "android-app://com.foo.me/"; |
| maybeSpeculateAndLaunchWithReferrers( |
| mTestServer.getURL(FRAGMENT_TEST_PAGE), true, referrer, referrer); |
| |
| Tab tab = getActivity().getActivityTab(); |
| // The tab hasn't been reloaded. |
| CriteriaHelper.pollInstrumentationThread( |
| new ElementContentCriteria(tab, "visibility", "hidden"), 2000, 200); |
| // The Referrer is correctly set. |
| CriteriaHelper.pollInstrumentationThread( |
| new TabsOpenedFromExternalAppTest.ReferrerCriteria(tab, referrer), 2000, 200); |
| } |
| |
| /** |
| * Tests that hidden tab accepts a referrer, and that this is dropped when the tab |
| * is launched with a mismatched referrer. |
| */ |
| @Test |
| @SmallTest |
| @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) |
| @RetryOnFailure |
| public void testHiddenTabWithMismatchedReferrers() throws Exception { |
| String prerenderReferrer = "android-app://com.foo.me/"; |
| String launchReferrer = "android-app://com.foo.me.i.changed.my.mind/"; |
| maybeSpeculateAndLaunchWithReferrers( |
| mTestServer.getURL(FRAGMENT_TEST_PAGE), true, prerenderReferrer, launchReferrer); |
| |
| Tab tab = getActivity().getActivityTab(); |
| // Prerender has been dropped. |
| CriteriaHelper.pollInstrumentationThread( |
| new ElementContentCriteria(tab, "visibility", "visible"), 2000, 200); |
| // The Referrer is correctly set. |
| CriteriaHelper.pollInstrumentationThread( |
| new TabsOpenedFromExternalAppTest.ReferrerCriteria(tab, launchReferrer), 2000, 200); |
| } |
| |
| /** Tests that a client can set a referrer, without speculating. */ |
| @Test |
| @SmallTest |
| @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) |
| @RetryOnFailure |
| public void testClientCanSetReferrer() throws Exception { |
| String referrerUrl = "android-app://com.foo.me/"; |
| maybeSpeculateAndLaunchWithReferrers(mTestPage, false, null, referrerUrl); |
| |
| Tab tab = getActivity().getActivityTab(); |
| // The Referrer is correctly set. |
| CriteriaHelper.pollInstrumentationThread( |
| new TabsOpenedFromExternalAppTest.ReferrerCriteria(tab, referrerUrl), 2000, 200); |
| } |
| |
| @Test |
| @MediumTest |
| public void testLaunchIncognitoURL() throws Exception { |
| Intent intent = createMinimalCustomTabIntent(); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| |
| final CustomTabActivity cctActivity = mCustomTabActivityTestRule.getActivity(); |
| final CallbackHelper mCctHiddenCallback = new CallbackHelper(); |
| final CallbackHelper mTabbedModeShownCallback = new CallbackHelper(); |
| final AtomicReference<ChromeTabbedActivity> tabbedActivity = new AtomicReference<>(); |
| |
| ActivityStateListener listener = (activity, newState) -> { |
| if (activity == cctActivity |
| && (newState == ActivityState.STOPPED || newState == ActivityState.DESTROYED)) { |
| mCctHiddenCallback.notifyCalled(); |
| } |
| |
| if (activity instanceof ChromeTabbedActivity && newState == ActivityState.RESUMED) { |
| mTabbedModeShownCallback.notifyCalled(); |
| tabbedActivity.set((ChromeTabbedActivity) activity); |
| } |
| }; |
| ApplicationStatus.registerStateListenerForAllActivities(listener); |
| |
| ThreadUtils.runOnUiThread(() -> { |
| cctActivity.getActivityTab().getTabWebContentsDelegateAndroid().openNewTab( |
| "about:blank", null, null, WindowOpenDisposition.OFF_THE_RECORD, false); |
| }); |
| |
| mCctHiddenCallback.waitForCallback("CCT not hidden.", 0); |
| mTabbedModeShownCallback.waitForCallback("Tabbed mode not shown.", 0); |
| |
| CriteriaHelper.pollUiThread( |
| Criteria.equals(true, () -> tabbedActivity.get().areTabModelsInitialized())); |
| |
| CriteriaHelper.pollUiThread(new Criteria() { |
| @Override |
| public boolean isSatisfied() { |
| Tab tab = tabbedActivity.get().getActivityTab(); |
| if (tab == null) { |
| updateFailureReason("Tab is null"); |
| return false; |
| } |
| if (!tab.isIncognito()) { |
| updateFailureReason("Incognito tab not selected"); |
| return false; |
| } |
| if (!TextUtils.equals(tab.getUrl(), "about:blank")) { |
| updateFailureReason("Wrong URL loaded in incognito tab"); |
| return false; |
| } |
| return true; |
| } |
| }); |
| |
| ApplicationStatus.unregisterActivityStateListener(listener); |
| } |
| |
| @Test |
| @MediumTest |
| public void testLaunchIncognitoCustomTabForPaymentRequest() throws Exception { |
| Intent intent = createMinimalCustomTabIntent(); |
| intent.putExtra(IntentHandler.EXTRA_OPEN_NEW_INCOGNITO_TAB, true); |
| CustomTabIntentDataProvider.addPaymentRequestUIExtras(intent); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| |
| Assert.assertTrue(mCustomTabActivityTestRule.getActivity().getActivityTab().isIncognito()); |
| } |
| |
| /** |
| * Tests that a Weblite URL from an external app uses the lite_url param when Data Reduction |
| * Proxy previews are being used. |
| */ |
| @Test |
| @SmallTest |
| @CommandLineFlags.Add("enable-spdy-proxy-auth") |
| @EnableFeatures( |
| {"DataReductionProxyDecidesTransform", "DataReductionProxyEnabledWithNetworkService"}) |
| @RetryOnFailure |
| public void |
| testLaunchWebLiteURL() throws Exception { |
| final String testUrl = WEBLITE_PREFIX + mTestPage; |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent( |
| CustomTabsTestUtils.createMinimalCustomTabIntent( |
| InstrumentationRegistry.getTargetContext(), testUrl)); |
| Tab tab = mCustomTabActivityTestRule.getActivity().getActivityTab(); |
| Assert.assertEquals(mTestPage, tab.getUrl()); |
| } |
| |
| /** |
| * Tests that a Weblite URL from an external app does not use the lite_url param when Data |
| * Reduction Proxy previews are not being used. |
| */ |
| @Test |
| @SmallTest |
| @CommandLineFlags.Add("enable-spdy-proxy-auth") |
| @DisableFeatures("DataReductionProxyDecidesTransform") |
| @RetryOnFailure |
| public void testLaunchWebLiteURLNoPreviews() throws Exception { |
| final String testUrl = WEBLITE_PREFIX + mTestPage; |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent( |
| CustomTabsTestUtils.createMinimalCustomTabIntent( |
| InstrumentationRegistry.getTargetContext(), testUrl)); |
| Tab tab = mCustomTabActivityTestRule.getActivity().getActivityTab(); |
| Assert.assertEquals(testUrl, tab.getUrl()); |
| } |
| |
| /** |
| * Tests that a Weblite URL from an external app does not use the lite_url param when Data |
| * Reduction Proxy is not being used. |
| */ |
| @Test |
| @SmallTest |
| @EnableFeatures("DataReductionProxyDecidesTransform") |
| @RetryOnFailure |
| public void testLaunchWebLiteURLNoDataReductionProxy() throws Exception { |
| final String testUrl = WEBLITE_PREFIX + mTestPage; |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent( |
| CustomTabsTestUtils.createMinimalCustomTabIntent( |
| InstrumentationRegistry.getTargetContext(), testUrl)); |
| Tab tab = mCustomTabActivityTestRule.getActivity().getActivityTab(); |
| Assert.assertEquals(testUrl, tab.getUrl()); |
| } |
| |
| /** |
| * Tests that a Weblite URL from an external app does not use the lite_url param when the param |
| * is an https URL. |
| */ |
| @Test |
| @SmallTest |
| @CommandLineFlags.Add("enable-spdy-proxy-auth") |
| @EnableFeatures("DataReductionProxyDecidesTransform") |
| @RetryOnFailure |
| public void testLaunchHttpsWebLiteURL() throws Exception { |
| final String testUrl = WEBLITE_PREFIX + mTestPage.replaceFirst("http", "https"); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent( |
| CustomTabsTestUtils.createMinimalCustomTabIntent( |
| InstrumentationRegistry.getTargetContext(), testUrl)); |
| Tab tab = mCustomTabActivityTestRule.getActivity().getActivityTab(); |
| Assert.assertEquals(testUrl, tab.getUrl()); |
| } |
| |
| /** |
| * Tests that a URL from an external app does not use the lite_url param when the prefix is not |
| * the WebLite url. |
| */ |
| @Test |
| @SmallTest |
| @CommandLineFlags.Add("enable-spdy-proxy-auth") |
| @EnableFeatures("DataReductionProxyDecidesTransform") |
| @RetryOnFailure |
| public void testLaunchNonWebLiteURL() throws Exception { |
| final String testUrl = mTestPage2 + "/?lite_url=" + mTestPage; |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent( |
| CustomTabsTestUtils.createMinimalCustomTabIntent( |
| InstrumentationRegistry.getTargetContext(), testUrl)); |
| Tab tab = mCustomTabActivityTestRule.getActivity().getActivityTab(); |
| Assert.assertEquals(testUrl, tab.getUrl()); |
| } |
| |
| /** Maybe prerenders a URL with a referrer, then launch it with another one. */ |
| private void maybeSpeculateAndLaunchWithReferrers(String url, boolean useHiddenTab, |
| String speculationReferrer, String launchReferrer) throws Exception { |
| Context context = InstrumentationRegistry.getInstrumentation() |
| .getTargetContext() |
| .getApplicationContext(); |
| Intent intent = CustomTabsTestUtils.createMinimalCustomTabIntent(context, url); |
| if (useHiddenTab) { |
| CustomTabsConnection connection = CustomTabsTestUtils.warmUpAndWait(); |
| CustomTabsSessionToken token = CustomTabsSessionToken.getSessionTokenFromIntent(intent); |
| connection.newSession(token); |
| connection.setCanUseHiddenTabForSession(token, true); |
| Bundle extras = null; |
| if (speculationReferrer != null) { |
| extras = new Bundle(); |
| extras.putParcelable(Intent.EXTRA_REFERRER, Uri.parse(speculationReferrer)); |
| } |
| Assert.assertTrue(connection.mayLaunchUrl(token, Uri.parse(url), extras, null)); |
| ensureCompletedSpeculationForUrl(connection, url); |
| } |
| |
| if (launchReferrer != null) { |
| intent.putExtra(Intent.EXTRA_REFERRER, Uri.parse(launchReferrer)); |
| } |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| } |
| |
| /** |
| * Test intended to verify the way we test history is correct. |
| * Start an activity and then navigate to a different url. |
| * We test NavigationController behavior through canGoBack/Forward as well |
| * as browser history through an HistoryProvider. |
| */ |
| @Test |
| @SmallTest |
| public void testHistoryNoSpeculation() throws Exception { |
| Context context = InstrumentationRegistry.getInstrumentation() |
| .getTargetContext() |
| .getApplicationContext(); |
| Intent intent = CustomTabsTestUtils.createMinimalCustomTabIntent(context, mTestPage); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| final Tab tab = mCustomTabActivityTestRule.getActivity().getActivityTab(); |
| ThreadUtils.runOnUiThreadBlocking( |
| (Runnable) () -> tab.loadUrl(new LoadUrlParams(mTestPage2))); |
| ChromeTabUtils.waitForTabPageLoaded(tab, mTestPage2); |
| |
| Assert.assertTrue(tab.canGoBack()); |
| Assert.assertFalse(tab.canGoForward()); |
| |
| List<HistoryItem> history = getHistory(); |
| Assert.assertEquals(2, history.size()); |
| Assert.assertEquals(mTestPage2, history.get(0).getUrl()); |
| Assert.assertEquals(mTestPage, history.get(1).getUrl()); |
| } |
| |
| /** |
| * The following test that history only has a single final page after speculation, |
| * whether it was a hit or a miss. |
| */ |
| @Test |
| @SmallTest |
| @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) |
| public void testHistoryAfterHiddenTabHit() throws Exception { |
| verifyHistoryAfterHiddenTab(true); |
| } |
| |
| @Test |
| @SmallTest |
| @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) |
| public void testHistoryAfterHiddenTabMiss() throws Exception { |
| verifyHistoryAfterHiddenTab(false); |
| } |
| |
| private void verifyHistoryAfterHiddenTab(boolean speculationWasAHit) throws Exception { |
| String speculationUrl = mTestPage; |
| String navigationUrl = speculationWasAHit ? mTestPage : mTestPage2; |
| final CustomTabsConnection connection = CustomTabsTestUtils.warmUpAndWait(); |
| Context context = InstrumentationRegistry.getInstrumentation() |
| .getTargetContext() |
| .getApplicationContext(); |
| Intent intent = CustomTabsTestUtils.createMinimalCustomTabIntent(context, navigationUrl); |
| CustomTabsSessionToken token = CustomTabsSessionToken.getSessionTokenFromIntent(intent); |
| connection.newSession(token); |
| connection.setCanUseHiddenTabForSession(token, true); |
| |
| Assert.assertTrue(connection.mayLaunchUrl(token, Uri.parse(speculationUrl), null, null)); |
| ensureCompletedSpeculationForUrl(connection, speculationUrl); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| |
| Tab tab = getActivity().getActivityTab(); |
| Assert.assertFalse(tab.canGoBack()); |
| Assert.assertFalse(tab.canGoForward()); |
| |
| List<HistoryItem> history = getHistory(); |
| Assert.assertEquals(1, history.size()); |
| Assert.assertEquals(navigationUrl, history.get(0).getUrl()); |
| } |
| |
| private void mayLaunchUrlWithoutWarmup(boolean useHiddenTab) throws InterruptedException { |
| Context context = InstrumentationRegistry.getInstrumentation() |
| .getTargetContext() |
| .getApplicationContext(); |
| CustomTabsConnection connection = CustomTabsTestUtils.setUpConnection(); |
| CustomTabsSessionToken token = CustomTabsSessionToken.createMockSessionTokenForTesting(); |
| connection.newSession(token); |
| Bundle extras = null; |
| connection.setCanUseHiddenTabForSession(token, useHiddenTab); |
| Assert.assertTrue(connection.mayLaunchUrl(token, Uri.parse(mTestPage), extras, null)); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent( |
| CustomTabsTestUtils.createMinimalCustomTabIntent(context, mTestPage)); |
| Tab tab = mCustomTabActivityTestRule.getActivity().getActivityTab(); |
| Assert.assertEquals(mTestPage, tab.getUrl()); |
| } |
| |
| private ChromeActivity reparentAndVerifyTab() throws InterruptedException { |
| final ActivityMonitor monitor = InstrumentationRegistry.getInstrumentation().addMonitor( |
| ChromeTabbedActivity.class.getName(), /* result = */ null, false); |
| final Tab tabToBeReparented = getActivity().getActivityTab(); |
| final CallbackHelper tabHiddenHelper = new CallbackHelper(); |
| TabObserver observer = new EmptyTabObserver() { |
| @Override |
| public void onHidden(Tab tab, @TabHidingType int type) { |
| tabHiddenHelper.notifyCalled(); |
| } |
| }; |
| tabToBeReparented.addObserver(observer); |
| ThreadUtils.postOnUiThread(() -> { |
| getActivity().openCurrentUrlInBrowser(true); |
| Assert.assertNull(getActivity().getActivityTab()); |
| }); |
| // Use the extended CriteriaHelper timeout to make sure we get an activity |
| final Activity lastActivity = |
| monitor.waitForActivityWithTimeout(CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL); |
| Assert.assertNotNull( |
| "Monitor did not get an activity before hitting the timeout", lastActivity); |
| Assert.assertTrue("Expected lastActivity to be a ChromeActivity, was " |
| + lastActivity.getClass().getName(), |
| lastActivity instanceof ChromeActivity); |
| final ChromeActivity newActivity = (ChromeActivity) lastActivity; |
| CriteriaHelper.pollUiThread((new Criteria() { |
| @Override |
| public boolean isSatisfied() { |
| return newActivity.getActivityTab() != null |
| && newActivity.getActivityTab().equals(tabToBeReparented); |
| } |
| })); |
| Assert.assertEquals(newActivity.getWindowAndroid(), tabToBeReparented.getWindowAndroid()); |
| Assert.assertEquals(newActivity.getWindowAndroid(), |
| tabToBeReparented.getWebContents().getTopLevelNativeWindow()); |
| Assert.assertFalse( |
| tabToBeReparented.getDelegateFactory() instanceof CustomTabDelegateFactory); |
| Assert.assertEquals("The tab should never be hidden during the reparenting process", 0, |
| tabHiddenHelper.getCallCount()); |
| Assert.assertFalse(tabToBeReparented.isCurrentlyACustomTab()); |
| tabToBeReparented.removeObserver(observer); |
| RewindableIterator<TabObserver> observers = TabTestUtils.getTabObservers(tabToBeReparented); |
| while (observers.hasNext()) { |
| Assert.assertFalse(observers.next() instanceof CustomTabObserver); |
| } |
| return newActivity; |
| } |
| |
| private void checkPageLoadMetrics(boolean allowMetrics) |
| throws InterruptedException, TimeoutException { |
| final AtomicReference<Long> firstContentfulPaintMs = new AtomicReference<>(-1L); |
| final AtomicReference<Long> activityStartTimeMs = new AtomicReference<>(-1L); |
| final AtomicReference<Long> loadEventStartMs = new AtomicReference<>(-1L); |
| final AtomicReference<Boolean> sawNetworkQualityEstimates = new AtomicReference<>(false); |
| |
| CustomTabsCallback cb = new CustomTabsCallback() { |
| @Override |
| public void extraCallback(String callbackName, Bundle args) { |
| if (callbackName.equals(CustomTabsConnection.ON_WARMUP_COMPLETED)) return; |
| |
| Assert.assertEquals(CustomTabsConnection.PAGE_LOAD_METRICS_CALLBACK, callbackName); |
| if (-1 != args.getLong(PageLoadMetrics.EFFECTIVE_CONNECTION_TYPE, -1)) { |
| sawNetworkQualityEstimates.set(true); |
| } |
| |
| long navigationStart = args.getLong(PageLoadMetrics.NAVIGATION_START, -1); |
| if (navigationStart == -1) { |
| // Untested metric callback. |
| return; |
| } |
| long current = SystemClock.uptimeMillis(); |
| Assert.assertTrue(navigationStart <= current); |
| Assert.assertTrue(navigationStart >= activityStartTimeMs.get()); |
| |
| long firstContentfulPaint = |
| args.getLong(PageLoadMetrics.FIRST_CONTENTFUL_PAINT, -1); |
| if (firstContentfulPaint > 0) { |
| Assert.assertTrue(firstContentfulPaint <= (current - navigationStart)); |
| firstContentfulPaintMs.set(firstContentfulPaint); |
| } |
| |
| long loadEventStart = args.getLong(PageLoadMetrics.LOAD_EVENT_START, -1); |
| if (loadEventStart > 0) { |
| Assert.assertTrue(loadEventStart <= (current - navigationStart)); |
| loadEventStartMs.set(loadEventStart); |
| } |
| } |
| }; |
| |
| CustomTabsSession session = CustomTabsTestUtils.bindWithCallback(cb); |
| Intent intent = new CustomTabsIntent.Builder(session).build().intent; |
| |
| if (allowMetrics) { |
| CustomTabsSessionToken token = CustomTabsSessionToken.getSessionTokenFromIntent(intent); |
| CustomTabsConnection connection = CustomTabsTestUtils.warmUpAndWait(); |
| connection.mClientManager.setShouldGetPageLoadMetricsForSession(token, true); |
| } |
| |
| intent.setData(Uri.parse(mTestPage)); |
| intent.setComponent(new ComponentName( |
| InstrumentationRegistry.getTargetContext(), ChromeLauncherActivity.class)); |
| intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| |
| activityStartTimeMs.set(SystemClock.uptimeMillis()); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent); |
| if (allowMetrics) { |
| CriteriaHelper.pollInstrumentationThread(() -> firstContentfulPaintMs.get() > 0); |
| CriteriaHelper.pollInstrumentationThread(() -> loadEventStartMs.get() > 0); |
| CriteriaHelper.pollInstrumentationThread(() -> sawNetworkQualityEstimates.get()); |
| } else { |
| try { |
| CriteriaHelper.pollInstrumentationThread(() -> firstContentfulPaintMs.get() > 0); |
| } catch (AssertionError e) { |
| // Expected. |
| } |
| Assert.assertEquals(-1L, (long) firstContentfulPaintMs.get()); |
| } |
| } |
| |
| private CustomTabsSessionToken warmUpAndLaunchUrlWithSession(Intent intentWithSession) |
| throws Exception { |
| CustomTabsConnection connection = CustomTabsTestUtils.warmUpAndWait(); |
| CustomTabsSessionToken token = |
| CustomTabsSessionToken.getSessionTokenFromIntent(intentWithSession); |
| connection.newSession(token); |
| intentWithSession.setData(Uri.parse(mTestPage)); |
| mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intentWithSession); |
| return token; |
| } |
| |
| private CustomTabsSessionToken warmUpAndLaunchUrlWithSession() throws Exception { |
| return warmUpAndLaunchUrlWithSession(CustomTabsTestUtils.createMinimalCustomTabIntent( |
| InstrumentationRegistry.getTargetContext(), mTestPage)); |
| } |
| |
| private static void ensureCompletedSpeculationForUrl( |
| final CustomTabsConnection connection, final String url) throws InterruptedException { |
| CriteriaHelper.pollUiThread(new Criteria("Tab was not created") { |
| @Override |
| public boolean isSatisfied() { |
| return connection.mSpeculation != null && connection.mSpeculation.tab != null; |
| } |
| }, LONG_TIMEOUT_MS, CriteriaHelper.DEFAULT_POLLING_INTERVAL); |
| ChromeTabUtils.waitForTabPageLoaded(connection.mSpeculation.tab, url); |
| } |
| |
| /** |
| * A helper class to monitor sending status of a {@link PendingIntent}. |
| */ |
| private class OnFinishedForTest implements PendingIntent.OnFinished { |
| |
| private final PendingIntent mPendingIntent; |
| private final CallbackHelper mCallbackHelper = new CallbackHelper(); |
| private Intent mCallbackIntent; |
| |
| /** |
| * Create an instance of {@link OnFinishedForTest}, testing the given {@link PendingIntent}. |
| */ |
| public OnFinishedForTest(PendingIntent pendingIntent) { |
| mPendingIntent = pendingIntent; |
| } |
| |
| public Intent getCallbackIntent() { |
| return mCallbackIntent; |
| } |
| |
| public void waitForCallback(String failureReason) |
| throws TimeoutException, InterruptedException { |
| mCallbackHelper.waitForCallback(failureReason, 0); |
| } |
| |
| @Override |
| public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode, |
| String resultData, Bundle resultExtras) { |
| if (pendingIntent.equals(mPendingIntent)) { |
| mCallbackIntent = intent; |
| mCallbackHelper.notifyCalled(); |
| } |
| } |
| } |
| |
| private static class ElementContentCriteria extends Criteria { |
| private final Tab mTab; |
| private final String mJsFunction; |
| private final String mExpected; |
| |
| public ElementContentCriteria(Tab tab, String elementId, String expected) { |
| super("Page element is not as expected."); |
| mTab = tab; |
| mExpected = "\"" + expected + "\""; |
| mJsFunction = "(function () { return document.getElementById(\"" + elementId |
| + "\").innerHTML; })()"; |
| } |
| |
| @Override |
| public boolean isSatisfied() { |
| String value; |
| try { |
| String jsonText = JavaScriptUtils.executeJavaScriptAndWaitForResult( |
| mTab.getWebContents(), mJsFunction); |
| if (jsonText.equalsIgnoreCase("null")) jsonText = ""; |
| value = jsonText; |
| } catch (InterruptedException | TimeoutException e) { |
| e.printStackTrace(); |
| return false; |
| } |
| boolean isSatisfied = TextUtils.equals(mExpected, value); |
| if (!isSatisfied) { |
| updateFailureReason("Page element is " + value + " instead of expected " + mExpected); |
| } |
| return isSatisfied; |
| } |
| } |
| |
| private static List<HistoryItem> getHistory() throws TimeoutException, InterruptedException { |
| final TestBrowsingHistoryObserver historyObserver = new TestBrowsingHistoryObserver(); |
| ThreadUtils.runOnUiThreadBlocking(() -> { |
| BrowsingHistoryBridge historyService = new BrowsingHistoryBridge(false); |
| historyService.setObserver(historyObserver); |
| String historyQueryFilter = ""; |
| historyService.queryHistory(historyQueryFilter); |
| }); |
| historyObserver.getQueryCallback().waitForCallback(0); |
| 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"); |
| } |
| } |
| |
| private void waitForModuleLoading() { |
| CriteriaHelper.pollUiThread(new Criteria() { |
| @Override |
| public boolean isSatisfied() { |
| if (!getActivity().getIntentDataProvider().isDynamicModuleEnabled()) return true; |
| |
| DynamicModuleCoordinator module = |
| mCustomTabActivityTestRule.getActivity().getComponent() |
| .resolveDynamicModuleCoordinator(); |
| return module != null && !module.isModuleLoading(); |
| } |
| }); |
| } |
| |
| private void setupHttpsTestServerAndPages() throws InterruptedException { |
| mTestServer.stopAndDestroyServer(); |
| // Module managed hosts only work with HTTPS. |
| mTestServer = EmbeddedTestServer.createAndStartHTTPSServer( |
| InstrumentationRegistry.getInstrumentation().getContext(), |
| ServerCertificate.CERT_OK); |
| mTestPage = mTestServer.getURL(TEST_PAGE); |
| mTestPage2 = mTestServer.getURL(TEST_PAGE_2); |
| } |
| } |