Index: chrome/android/javatests/src/org/chromium/chrome/browser/document/DocumentModeTest.java |
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/document/DocumentModeTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/document/DocumentModeTest.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..46e05ab92586234114e44696137646cb6897e1c1 |
--- /dev/null |
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/document/DocumentModeTest.java |
@@ -0,0 +1,879 @@ |
+// 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.document; |
+ |
+import static org.chromium.base.test.util.Restriction.RESTRICTION_TYPE_LOW_END_DEVICE; |
+ |
+import android.app.Activity; |
+import android.app.ActivityManager; |
+import android.app.ActivityManager.RunningServiceInfo; |
+import android.app.Instrumentation; |
+import android.app.PendingIntent; |
+import android.content.Context; |
+import android.content.Intent; |
+import android.net.Uri; |
+import android.os.Build; |
+import android.test.MoreAsserts; |
+import android.test.suitebuilder.annotation.MediumTest; |
+import android.view.ContextMenu; |
+import android.view.View; |
+ |
+import com.google.android.apps.chrome.R; |
+ |
+import org.chromium.base.ApplicationStatus; |
+import org.chromium.base.ThreadUtils; |
+import org.chromium.base.test.util.MinAndroidSdkLevel; |
+import org.chromium.base.test.util.Restriction; |
+import org.chromium.base.test.util.UrlUtils; |
+import org.chromium.chrome.browser.BookmarkUtils; |
+import org.chromium.chrome.browser.ChromeActivity; |
+import org.chromium.chrome.browser.ChromeMobileApplication; |
+import org.chromium.chrome.browser.ChromeTabbedActivity; |
+import org.chromium.chrome.browser.EmptyTabObserver; |
+import org.chromium.chrome.browser.IntentHandler; |
+import org.chromium.chrome.browser.Tab; |
+import org.chromium.chrome.browser.tabmodel.EmptyTabModelSelectorObserver; |
+import org.chromium.chrome.browser.tabmodel.TabModel; |
+import org.chromium.chrome.browser.tabmodel.TabModelUtils; |
+import org.chromium.chrome.browser.tabmodel.document.DocumentTabModel; |
+import org.chromium.chrome.browser.tabmodel.document.DocumentTabModel.InitializationObserver; |
+import org.chromium.chrome.browser.tabmodel.document.DocumentTabModelImpl; |
+import org.chromium.chrome.browser.tabmodel.document.DocumentTabModelSelector; |
+import org.chromium.chrome.browser.tabmodel.document.OffTheRecordDocumentTabModel; |
+import org.chromium.chrome.test.MultiActivityTestBase; |
+import org.chromium.chrome.test.util.ActivityUtils; |
+import org.chromium.chrome.test.util.DisableInTabbedMode; |
+import org.chromium.content.app.SandboxedProcessService; |
+import org.chromium.content.browser.test.util.CallbackHelper; |
+import org.chromium.content.browser.test.util.Criteria; |
+import org.chromium.content.browser.test.util.CriteriaHelper; |
+import org.chromium.content.browser.test.util.TouchCommon; |
+import org.chromium.content_public.browser.LoadUrlParams; |
+import org.chromium.ui.base.PageTransition; |
+ |
+import java.lang.ref.WeakReference; |
+import java.util.List; |
+import java.util.concurrent.Callable; |
+ |
+/** |
+ * Tests the interactions of the DocumentTabModel with Android's ActivityManager. This test suite |
+ * actually fires Intents to start different DocumentActivities. |
+ * |
+ * Note that this is different instrumenting one DocumentActivity in particular, which should be |
+ * tested using the DocumentActivityTestBase class. |
+ */ |
+@MinAndroidSdkLevel(Build.VERSION_CODES.LOLLIPOP) |
+@DisableInTabbedMode |
+public class DocumentModeTest extends MultiActivityTestBase { |
+ private static final String URL_1 = "data:text/html;charset=utf-8,Page%201"; |
+ private static final String URL_2 = "data:text/html;charset=utf-8,Page%202"; |
+ private static final String URL_3 = "data:text/html;charset=utf-8,Page%203"; |
+ private static final String URL_4 = "data:text/html;charset=utf-8,Page%204"; |
+ |
+ private static final String HTML_LINK = "<html><head><meta " |
+ + "name='viewport' content='width=device-width initial-scale=0.5, maximum-scale=0.5'>" |
+ + "<style>body {margin: 0em;} div {width: 100%; height: 100%; background: #011684;}" |
+ + "</style></head><body><a href='data:text/html;charset=utf-8,white' target='_blank'>" |
+ + "<div></div></a></body></html>"; |
+ private static final float HTML_SCALE = 0.5f; |
+ |
+ private boolean mInitializationCompleted; |
+ |
+ private Context mContext; |
+ private String mUrl; |
+ private String mReferrer; |
+ |
+ private static class TestTabObserver extends EmptyTabObserver { |
+ private ContextMenu mContextMenu; |
+ |
+ @Override |
+ public void onContextMenuShown(Tab tab, ContextMenu menu) { |
+ mContextMenu = menu; |
+ } |
+ } |
+ |
+ private static void launchMainIntent(Context context) throws Exception { |
+ Intent intent = new Intent(Intent.ACTION_MAIN); |
+ intent.setPackage(context.getPackageName()); |
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
+ context.startActivity(intent); |
+ MultiActivityTestBase.waitUntilChromeInForeground(); |
+ } |
+ |
+ private static void fireViewIntent(Context context, Uri data) throws Exception { |
+ Intent intent = new Intent(Intent.ACTION_VIEW, data); |
+ intent.setClassName(context.getPackageName(), ChromeLauncherActivity.class.getName()); |
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
+ context.startActivity(intent); |
+ MultiActivityTestBase.waitUntilChromeInForeground(); |
+ } |
+ |
+ /** |
+ * Launches three tabs via Intents with ACTION_VIEW. |
+ */ |
+ private int[] launchThreeTabs() throws Exception { |
+ int[] tabIds = new int[3]; |
+ tabIds[0] = launchViaViewIntent(false, URL_1); |
+ tabIds[1] = launchViaViewIntent(false, URL_2); |
+ tabIds[2] = launchViaViewIntent(false, URL_3); |
+ assertFalse(tabIds[0] == tabIds[1]); |
+ assertFalse(tabIds[0] == tabIds[2]); |
+ assertFalse(tabIds[1] == tabIds[2]); |
+ assertEquals(3, |
+ ChromeMobileApplication.getDocumentTabModelSelector().getModel(false).getCount()); |
+ return tabIds; |
+ } |
+ |
+ @Override |
+ public void setUp() throws Exception { |
+ super.setUp(); |
+ mContext = getInstrumentation().getTargetContext(); |
+ } |
+ |
+ /** |
+ * Confirm that you can't start ChromeTabbedActivity while the user is running in Document mode. |
+ */ |
+ @MediumTest |
+ public void testDontStartTabbedActivityInDocumentMode() throws Exception { |
+ launchThreeTabs(); |
+ |
+ // Try launching a ChromeTabbedActivity. |
+ Runnable runnable = new Runnable() { |
+ @Override |
+ public void run() { |
+ Intent intent = new Intent(); |
+ intent.setAction(Intent.ACTION_VIEW); |
+ intent.setClassName(mContext, ChromeTabbedActivity.class.getName()); |
+ intent.setData(Uri.parse(URL_1)); |
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT); |
+ mContext.startActivity(intent); |
+ } |
+ }; |
+ ActivityUtils.waitForActivity(getInstrumentation(), ChromeTabbedActivity.class, runnable); |
+ |
+ // ApplicationStatus should note that the ChromeTabbedActivity isn't running anymore. |
+ assertTrue(CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ List<WeakReference<Activity>> activities = ApplicationStatus.getRunningActivities(); |
+ for (WeakReference<Activity> activity : activities) { |
+ if (activity.get() instanceof ChromeTabbedActivity) return false; |
+ } |
+ return true; |
+ } |
+ })); |
+ } |
+ |
+ /** |
+ * Confirm that firing an Intent without a properly formatted document://ID?url scheme causes |
+ * the DocumentActivity to finish itself and hopefully not flat crash (though that'd be better |
+ * than letting the user live in both tabbed and document mode simultaneously crbug.com/445136). |
+ */ |
+ @MediumTest |
+ public void testFireInvalidIntent() throws Exception { |
+ launchThreeTabs(); |
+ |
+ final DocumentTabModelSelector selector = |
+ ChromeMobileApplication.getDocumentTabModelSelector(); |
+ final Activity lastTrackedActivity = ApplicationStatus.getLastTrackedFocusedActivity(); |
+ |
+ // Send the user home, then fire an Intent with invalid data. |
+ MultiActivityTestBase.launchHomescreenIntent(mContext); |
+ Intent intent = new Intent(lastTrackedActivity.getIntent()); |
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT); |
+ intent.setData(Uri.parse("toteslegitscheme://")); |
+ mContext.startActivity(intent); |
+ |
+ // A DocumentActivity gets started, but it should immediately call finishAndRemoveTask() |
+ // because of the broken Intent. |
+ assertTrue(CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ Activity activity = ApplicationStatus.getLastTrackedFocusedActivity(); |
+ return activity != lastTrackedActivity; |
+ } |
+ })); |
+ |
+ // We shouldn't record that a new Tab exists. |
+ assertTrue(CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ return selector.getCurrentModel().getCount() == 3 |
+ && selector.getTotalTabCount() == 3; |
+ } |
+ })); |
+ } |
+ |
+ /** |
+ * Confirm that firing an Intent for a document that has an ID for an already existing Tab kills |
+ * the original. |
+ */ |
+ @MediumTest |
+ public void testDuplicateTabIDsKillsOldActivities() throws Exception { |
+ launchThreeTabs(); |
+ |
+ final DocumentTabModelSelector selector = |
+ ChromeMobileApplication.getDocumentTabModelSelector(); |
+ final int lastTabId = selector.getCurrentTabId(); |
+ final Activity lastTrackedActivity = ApplicationStatus.getLastTrackedFocusedActivity(); |
+ |
+ // Send the user home, then fire an Intent with an old Tab ID and a new URL. |
+ MultiActivityTestBase.launchHomescreenIntent(mContext); |
+ Intent intent = new Intent(lastTrackedActivity.getIntent()); |
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT); |
+ intent.setData(Uri.parse("document://" + lastTabId + "?" + URL_4)); |
+ mContext.startActivity(intent); |
+ |
+ // Funnily enough, Android doesn't differentiate between URIs with different queries when |
+ // refocusing Activities based on the Intent data. This means we can't do a check to see |
+ // that the new Activity appears with URL_4 -- we just get a new instance of URL_3. |
+ assertTrue(CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ Activity activity = ApplicationStatus.getLastTrackedFocusedActivity(); |
+ if (!(activity instanceof ChromeActivity)) return false; |
+ |
+ ChromeActivity chromeActivity = (ChromeActivity) activity; |
+ Tab tab = chromeActivity.getActivityTab(); |
+ return tab != null && lastTrackedActivity != activity |
+ && !selector.isIncognitoSelected() |
+ && lastTabId == selector.getCurrentTabId(); |
+ } |
+ })); |
+ |
+ // Although we get a new DocumentActivity, the old one with the same tab ID gets killed. |
+ assertTrue(CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ return selector.getCurrentModel().getCount() == 3 |
+ && selector.getTotalTabCount() == 3; |
+ } |
+ })); |
+ } |
+ |
+ /** |
+ * Confirm that firing a View Intent with a null URL acts like a Main Intent. |
+ */ |
+ @MediumTest |
+ public void testRelaunchLatestTabWithInvalidViewIntent() throws Exception { |
+ launchThreeTabs(); |
+ |
+ final DocumentTabModelSelector selector = |
+ ChromeMobileApplication.getDocumentTabModelSelector(); |
+ final int lastTabId = selector.getCurrentTabId(); |
+ |
+ final Activity lastTrackedActivity = ApplicationStatus.getLastTrackedFocusedActivity(); |
+ |
+ // Send Chrome to the background, then bring it back. |
+ MultiActivityTestBase.launchHomescreenIntent(mContext); |
+ fireViewIntent(mContext, null); |
+ |
+ assertTrue(CriteriaHelper.pollForCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ return lastTrackedActivity == ApplicationStatus.getLastTrackedFocusedActivity() |
+ && !selector.isIncognitoSelected() |
+ && lastTabId == selector.getCurrentTabId(); |
+ } |
+ })); |
+ |
+ assertEquals(3, selector.getCurrentModel().getCount()); |
+ assertEquals(3, selector.getTotalTabCount()); |
+ } |
+ |
+ /** |
+ * Confirm that clicking the Chrome icon (e.g. firing an Intent with ACTION_MAIN) brings back |
+ * the last viewed Tab. |
+ */ |
+ @MediumTest |
+ public void testRelaunchLatestTab() throws Exception { |
+ launchThreeTabs(); |
+ |
+ final DocumentTabModelSelector selector = |
+ ChromeMobileApplication.getDocumentTabModelSelector(); |
+ final int lastTabId = selector.getCurrentTabId(); |
+ |
+ // Send Chrome to the background, then bring it back. |
+ MultiActivityTestBase.launchHomescreenIntent(mContext); |
+ launchMainIntent(mContext); |
+ |
+ assertTrue(CriteriaHelper.pollForCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ return !selector.isIncognitoSelected() && lastTabId == selector.getCurrentTabId(); |
+ } |
+ })); |
+ |
+ assertEquals(3, selector.getCurrentModel().getCount()); |
+ assertEquals(3, selector.getTotalTabCount()); |
+ } |
+ |
+ @MediumTest |
+ public void testReferrerExtra() throws Exception { |
+ Instrumentation.ActivityMonitor monitor = getInstrumentation().addMonitor( |
+ DocumentActivity.class.getName(), null, false); |
+ launchMainIntent(mContext); |
+ |
+ // Wait for tab model to become initialized. |
+ assertTrue(CriteriaHelper.pollForCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ return ChromeMobileApplication.isDocumentTabModelSelectorInitializedForTests(); |
+ } |
+ })); |
+ |
+ DocumentTabModelSelector selector = ChromeMobileApplication.getDocumentTabModelSelector(); |
+ selector.addObserver(new EmptyTabModelSelectorObserver() { |
+ @Override |
+ public void onNewTabCreated(Tab tab) { |
+ tab.addObserver(new EmptyTabObserver() { |
+ @Override |
+ public void onLoadUrl(Tab tab, LoadUrlParams params, int loadType) { |
+ mUrl = params.getUrl(); |
+ if (params.getReferrer() != null) { |
+ mReferrer = params.getReferrer().getUrl(); |
+ } |
+ } |
+ }); |
+ } |
+ }); |
+ |
+ // Wait for document activity from the main intent to launch before firing the next |
+ // intent. |
+ DocumentActivity doc = (DocumentActivity) monitor.waitForActivityWithTimeout(500); |
+ assertNotNull("DocumentActivity did not start", doc); |
+ |
+ // Fire the Intent with EXTRA_REFERRER. |
+ Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(URL_1)); |
+ intent.setClassName(mContext.getPackageName(), ChromeLauncherActivity.class.getName()); |
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
+ intent.putExtra(Intent.EXTRA_REFERRER, Uri.parse(URL_2)); |
+ IntentHandler.addTrustedIntentExtras(intent, mContext); |
+ mContext.startActivity(intent); |
+ |
+ assertTrue(CriteriaHelper.pollForCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ return URL_1.equals(mUrl); |
+ } |
+ })); |
+ |
+ assertEquals(URL_2, mReferrer); |
+ } |
+ |
+ /** |
+ * Confirm that setting the index brings the correct tab forward. |
+ */ |
+ @MediumTest |
+ public void testSetIndex() throws Exception { |
+ int[] tabIds = launchThreeTabs(); |
+ |
+ final DocumentTabModelSelector selector = |
+ ChromeMobileApplication.getDocumentTabModelSelector(); |
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
+ @Override |
+ public void run() { |
+ TabModelUtils.setIndex(selector.getCurrentModel(), 0); |
+ } |
+ }); |
+ |
+ assertTrue(CriteriaHelper.pollForCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ return !selector.isIncognitoSelected() && selector.getCurrentModel().index() == 0; |
+ } |
+ })); |
+ |
+ assertEquals(3, selector.getCurrentModel().getCount()); |
+ assertEquals(3, selector.getTotalTabCount()); |
+ assertEquals(tabIds[0], selector.getCurrentTab().getId()); |
+ } |
+ |
+ /** Check that Intents that request reusing Tabs are honored. */ |
+ @MediumTest |
+ public void testReuseIntent() throws Exception { |
+ // Create a tab, then send the user back to the Home screen. |
+ int tabId = launchViaViewIntent(false, URL_1); |
+ assertTrue(ChromeMobileApplication.isDocumentTabModelSelectorInitializedForTests()); |
+ final DocumentTabModelSelector selector = |
+ ChromeMobileApplication.getDocumentTabModelSelector(); |
+ assertEquals(1, selector.getModel(false).getCount()); |
+ launchHomescreenIntent(mContext); |
+ |
+ // Fire an Intent to reuse the same tab as before. |
+ Runnable runnable = new Runnable() { |
+ @Override |
+ public void run() { |
+ Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(URL_1)); |
+ intent.setClass(mContext, ChromeLauncherActivity.class); |
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
+ intent.putExtra(BookmarkUtils.REUSE_URL_MATCHING_TAB_ELSE_NEW_TAB, true); |
+ mContext.startActivity(intent); |
+ } |
+ }; |
+ ActivityUtils.waitForActivity(getInstrumentation(), ChromeLauncherActivity.class, runnable); |
+ waitUntilChromeInForeground(); |
+ assertTrue(CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ Activity lastActivity = ApplicationStatus.getLastTrackedFocusedActivity(); |
+ return lastActivity instanceof DocumentActivity; |
+ } |
+ })); |
+ assertEquals(tabId, selector.getCurrentTabId()); |
+ assertFalse(selector.isIncognitoSelected()); |
+ |
+ // Create another tab. |
+ final int secondTabId = launchViaViewIntent(false, URL_2); |
+ assertTrue(CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ return selector.getModel(false).getCount() == 2 |
+ && selector.getCurrentTabId() == secondTabId; |
+ } |
+ })); |
+ } |
+ |
+ /** |
+ * Tests both ways of launching Incognito tabs: via an Intent, and via |
+ * {@ref ChromeLauncherActivity#launchDocumentInstance()}. |
+ */ |
+ @MediumTest |
+ public void testIncognitoLaunches() throws Exception { |
+ assertFalse(ChromeMobileApplication.isDocumentTabModelSelectorInitializedForTests()); |
+ |
+ // Make sure that an untrusted Intent can't launch an IncognitoDocumentActivity. |
+ Instrumentation.ActivityMonitor monitor = getInstrumentation().addMonitor( |
+ DocumentActivity.class.getName(), null, false); |
+ assertFalse(ChromeMobileApplication.isDocumentTabModelSelectorInitializedForTests()); |
+ assertEquals(0, ApplicationStatus.getRunningActivities().size()); |
+ Runnable runnable = new Runnable() { |
+ @Override |
+ public void run() { |
+ Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(URL_1)); |
+ intent.setClass(mContext, ChromeLauncherActivity.class); |
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
+ intent.putExtra(IntentHandler.EXTRA_OPEN_NEW_INCOGNITO_TAB, true); |
+ mContext.startActivity(intent); |
+ } |
+ }; |
+ ActivityUtils.waitForActivity(getInstrumentation(), ChromeLauncherActivity.class, runnable); |
+ DocumentActivity doc = (DocumentActivity) monitor.waitForActivityWithTimeout(1000); |
+ assertNull(doc); |
+ |
+ // Create an Incognito tab via an Intent extra. |
+ final int firstId = launchViaViewIntent(true, URL_2); |
+ assertTrue(ChromeMobileApplication.isDocumentTabModelSelectorInitializedForTests()); |
+ final DocumentTabModelSelector selector = |
+ ChromeMobileApplication.getDocumentTabModelSelector(); |
+ final TabModel incognitoModel = selector.getModel(true); |
+ assertTrue(CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ return firstId == selector.getCurrentTabId() && selector.getTotalTabCount() == 1; |
+ } |
+ })); |
+ assertEquals(incognitoModel, selector.getCurrentModel()); |
+ |
+ // Launch via ChromeLauncherActivity.launchInstance(). |
+ final int secondId = launchViaLaunchDocumentInstance(true, URL_3); |
+ assertTrue(CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ return secondId == selector.getCurrentTabId() && selector.getTotalTabCount() == 2; |
+ } |
+ })); |
+ assertTrue(selector.isIncognitoSelected()); |
+ assertEquals(incognitoModel, selector.getCurrentModel()); |
+ assertEquals(secondId, TabModelUtils.getCurrentTabId(incognitoModel)); |
+ } |
+ |
+ /** |
+ * Confirm that the incognito tabs and TabModel are destroyed when the "close all" notification |
+ * Intent is fired. |
+ */ |
+ @MediumTest |
+ public void testIncognitoNotificationClosesTabs() throws Exception { |
+ final int regularId = launchViaLaunchDocumentInstance(false, URL_1); |
+ final DocumentTabModelSelector selector = |
+ ChromeMobileApplication.getDocumentTabModelSelector(); |
+ assertFalse(selector.isIncognitoSelected()); |
+ |
+ final int incognitoId = launchViaLaunchDocumentInstance(true, URL_2); |
+ assertTrue(CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ return selector.isIncognitoSelected() && selector.getCurrentTabId() == incognitoId; |
+ } |
+ })); |
+ assertEquals(0, selector.getCurrentModel().index()); |
+ assertEquals(1, selector.getCurrentModel().getCount()); |
+ |
+ PendingIntent closeAllIntent = |
+ ChromeLauncherActivity.getRemoveAllIncognitoTabsIntent(mContext); |
+ closeAllIntent.send(); |
+ |
+ assertTrue(CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ return selector.getCurrentTabId() == regularId; |
+ } |
+ })); |
+ OffTheRecordDocumentTabModel tabModel = |
+ (OffTheRecordDocumentTabModel) selector.getModel(true); |
+ assertFalse(selector.isIncognitoSelected()); |
+ assertFalse(tabModel.isDocumentTabModelImplCreated()); |
+ } |
+ |
+ /** |
+ * Tests if a tab is covered by its child activity. |
+ */ |
+ @MediumTest |
+ public void testCoveredByChildActivity() throws Exception { |
+ final int tabId = launchViaLaunchDocumentInstance(false, URL_1); |
+ final DocumentTabModel model = |
+ ChromeMobileApplication.getDocumentTabModelSelector().getModelForTabId(tabId); |
+ final Tab tab = model.getTabAt(0); |
+ assertTrue(tab instanceof DocumentTab); |
+ final DocumentTab documentTab = (DocumentTab) tab; |
+ |
+ // We need to wait until the UI for document tab is initialized. So we create the |
+ // InitializationObserver and set its satisfied criteria the same as |
+ // DocumentActivity.mTabInitializationObserver. |
+ InitializationObserver observer = new InitializationObserver(model) { |
+ @Override |
+ public boolean isSatisfied(int currentState) { |
+ return currentState >= DocumentTabModelImpl.STATE_LOAD_TAB_STATE_BG_END |
+ || model.isTabStateReady(tabId); |
+ } |
+ |
+ @Override |
+ public boolean isCanceled() { |
+ return false; |
+ } |
+ |
+ @Override |
+ public void runImmediately() { |
+ // This observer is created before DocumentActivity.mTabInitializationObserver. |
+ // Postpone setting mInitializationCompleted afterwards. |
+ ThreadUtils.postOnUiThread(new Runnable() { |
+ @Override |
+ public void run() { |
+ mInitializationCompleted = true; |
+ } |
+ }); |
+ } |
+ }; |
+ observer.runWhenReady(); |
+ |
+ assertTrue(CriteriaHelper.pollForCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ return mInitializationCompleted; |
+ } |
+ })); |
+ |
+ assertFalse(documentTab.isCoveredByChildActivity()); |
+ assertFalse(model.isCoveredByChildActivity(tabId)); |
+ |
+ documentTab.setCoveredByChildActivity(true); |
+ assertTrue(documentTab.isCoveredByChildActivity()); |
+ assertTrue(model.isCoveredByChildActivity(tabId)); |
+ |
+ documentTab.setCoveredByChildActivity(false); |
+ assertFalse(documentTab.isCoveredByChildActivity()); |
+ assertFalse(model.isCoveredByChildActivity(tabId)); |
+ } |
+ |
+ /** |
+ * Tests that tab ID is properly set when tabs change. |
+ */ |
+ @MediumTest |
+ public void testLastTabIdUpdates() throws Exception { |
+ launchLinkDocument(); |
+ |
+ final DocumentActivity firstActivity = |
+ (DocumentActivity) ApplicationStatus.getLastTrackedFocusedActivity(); |
+ |
+ // Save the current tab ID. |
+ final DocumentTabModelSelector selector = |
+ ChromeMobileApplication.getDocumentTabModelSelector(); |
+ final TabModel tabModel = selector.getModel(false); |
+ final int firstTabId = selector.getCurrentTabId(); |
+ final int firstTabIndex = tabModel.index(); |
+ |
+ openLinkInBackgroundTab(); |
+ |
+ // Do a plain click to make the link open in a new foreground Document. |
+ Runnable fgTrigger = new Runnable() { |
+ @Override |
+ public void run() { |
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
+ @Override |
+ public void run() { |
+ View view = firstActivity.findViewById(android.R.id.content).getRootView(); |
+ TouchCommon.singleClickView(view); |
+ } |
+ }); |
+ } |
+ }; |
+ ActivityUtils.waitForActivity(getInstrumentation(), DocumentActivity.class, fgTrigger); |
+ assertTrue(CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ if (3 != tabModel.getCount()) return false; |
+ if (firstTabIndex == tabModel.index()) return false; |
+ if (firstTabId == selector.getCurrentTabId()) return false; |
+ return true; |
+ } |
+ })); |
+ MoreAsserts.assertNotEqual( |
+ firstActivity, ApplicationStatus.getLastTrackedFocusedActivity()); |
+ } |
+ |
+ @Restriction(RESTRICTION_TYPE_LOW_END_DEVICE) |
+ @MediumTest |
+ public void testNewTabLoadLowEnd() throws Exception { |
+ launchLinkDocument(); |
+ |
+ final CallbackHelper tabCreatedCallback = new CallbackHelper(); |
+ final CallbackHelper tabLoadStartedCallback = new CallbackHelper(); |
+ |
+ final DocumentTabModelSelector selector = |
+ ChromeMobileApplication.getDocumentTabModelSelector(); |
+ selector.addObserver(new EmptyTabModelSelectorObserver() { |
+ @Override |
+ public void onNewTabCreated(final Tab newTab) { |
+ selector.removeObserver(this); |
+ tabCreatedCallback.notifyCalled(); |
+ |
+ assertFalse(newTab.getWebContents().isLoadingToDifferentDocument()); |
+ |
+ newTab.addObserver(new EmptyTabObserver() { |
+ @Override |
+ public void onPageLoadStarted(Tab tab) { |
+ newTab.removeObserver(this); |
+ tabLoadStartedCallback.notifyCalled(); |
+ } |
+ }); |
+ } |
+ }); |
+ |
+ openLinkInBackgroundTab(); |
+ |
+ // Tab should be created, but shouldn't start loading until we switch to it. |
+ assertEquals(1, tabCreatedCallback.getCallCount()); |
+ assertEquals(0, tabLoadStartedCallback.getCallCount()); |
+ |
+ TabModelUtils.setIndex(selector.getCurrentModel(), 1); |
+ tabLoadStartedCallback.waitForCallback(0); |
+ } |
+ |
+ /** |
+ * Tests that "Open in new tab" command doesn't create renderer per tab |
+ * on low end devices. |
+ */ |
+ @Restriction(RESTRICTION_TYPE_LOW_END_DEVICE) |
+ @MediumTest |
+ public void testNewTabRenderersLowEnd() throws Exception { |
+ launchLinkDocument(); |
+ |
+ // Ignore any side effects that the first background tab might produce. |
+ openLinkInBackgroundTab(); |
+ |
+ int rendererCountBefore = countRenderers(); |
+ |
+ final int newTabCount = 5; |
+ for (int i = 0; i != newTabCount; ++i) { |
+ openLinkInBackgroundTab(); |
+ } |
+ |
+ int rendererCountAfter = countRenderers(); |
+ |
+ assertEquals(rendererCountBefore, rendererCountAfter); |
+ } |
+ |
+ /** Starts a DocumentActivity by using firing a VIEW Intent. */ |
+ private int launchViaViewIntent(final boolean incognito, final String url) throws Exception { |
+ // Fire the Intent and wait until Chrome is in the foreground. |
+ Runnable runnable = new Runnable() { |
+ @Override |
+ public void run() { |
+ Runnable runnable = new Runnable() { |
+ @Override |
+ public void run() { |
+ Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(URL_1)); |
+ intent.setClass(mContext, ChromeLauncherActivity.class); |
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
+ if (incognito) { |
+ intent.putExtra(IntentHandler.EXTRA_OPEN_NEW_INCOGNITO_TAB, true); |
+ IntentHandler.startActivityForTrustedIntent(intent, mContext); |
+ } else { |
+ mContext.startActivity(intent); |
+ } |
+ } |
+ }; |
+ ActivityUtils.waitForActivity(getInstrumentation(), |
+ (incognito ? IncognitoDocumentActivity.class : DocumentActivity.class), |
+ runnable); |
+ } |
+ }; |
+ return launchUrlViaRunnable(incognito, runnable); |
+ } |
+ |
+ /** Starts a DocumentActivity using {@ref ChromeLauncherActivity.launchDocumentInstance().} */ |
+ private int launchViaLaunchDocumentInstance( |
+ final boolean incognito, final String url) throws Exception { |
+ Runnable runnable = new Runnable() { |
+ @Override |
+ public void run() { |
+ ChromeLauncherActivity.launchDocumentInstance(null, incognito, |
+ ChromeLauncherActivity.LAUNCH_MODE_FOREGROUND, url, |
+ DocumentMetricIds.STARTED_BY_UNKNOWN, PageTransition.LINK, false, null); |
+ } |
+ }; |
+ return launchUrlViaRunnable(incognito, runnable); |
+ } |
+ |
+ /** |
+ * Launches a DocumentActivity via the given Runnable. |
+ * Ideally this would use Observers, but we can't know that the DocumentTabModelSelector has |
+ * been created, and don't want to influence the test by accidentally creating it during the |
+ * test suite runs. |
+ * @return ID of the Tab that was launched. |
+ */ |
+ private int launchUrlViaRunnable(final boolean incognito, final Runnable runnable) |
+ throws Exception { |
+ final int tabCount = |
+ ChromeMobileApplication.isDocumentTabModelSelectorInitializedForTests() |
+ ? ChromeMobileApplication.getDocumentTabModelSelector().getModel(incognito) |
+ .getCount() : 0; |
+ final int tabId = |
+ ChromeMobileApplication.isDocumentTabModelSelectorInitializedForTests() |
+ ? ChromeMobileApplication.getDocumentTabModelSelector().getCurrentTabId() |
+ : Tab.INVALID_TAB_ID; |
+ |
+ runnable.run(); |
+ assertTrue(ChromeMobileApplication.isDocumentTabModelSelectorInitializedForTests()); |
+ MultiActivityTestBase.waitUntilChromeInForeground(); |
+ |
+ // Wait until the selector is ready and the Tabs have been added to the DocumentTabModel. |
+ assertTrue(CriteriaHelper.pollForCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ if (!ChromeMobileApplication.isDocumentTabModelSelectorInitializedForTests()) { |
+ return false; |
+ } |
+ |
+ DocumentTabModelSelector selector = |
+ ChromeMobileApplication.getDocumentTabModelSelector(); |
+ if (selector.isIncognitoSelected() != incognito) return false; |
+ if (selector.getModel(incognito).getCount() != (tabCount + 1)) return false; |
+ if (selector.getCurrentTabId() == tabId) return false; |
+ return true; |
+ } |
+ })); |
+ |
+ return ChromeMobileApplication.getDocumentTabModelSelector().getCurrentTabId(); |
+ } |
+ |
+ /** |
+ * Launches DocumentActivity with the special URL suitable for openLinkInBackgroundTab() |
+ * invocations. |
+ */ |
+ private void launchLinkDocument() throws Exception { |
+ // Load HTML that defines one gigantic link spanning the whole page. |
+ launchViaLaunchDocumentInstance(false, UrlUtils.encodeHtmlDataUri(HTML_LINK)); |
+ final DocumentActivity activity = |
+ (DocumentActivity) ApplicationStatus.getLastTrackedFocusedActivity(); |
+ assertTrue(CriteriaHelper.pollForCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ return Float.compare( |
+ activity.getCurrentContentViewCore().getScale(), HTML_SCALE) == 0; |
+ } |
+ })); |
+ } |
+ |
+ /** |
+ * Long presses at the center of the page, selects "Open In New Tab" option |
+ * from the menu. |
+ */ |
+ private void openLinkInBackgroundTab() throws Exception { |
+ // Long press the center of the page, which should bring up the context menu. |
+ final TestTabObserver observer = new TestTabObserver(); |
+ final DocumentActivity activity = |
+ (DocumentActivity) ApplicationStatus.getLastTrackedFocusedActivity(); |
+ activity.getActivityTab().addObserver(observer); |
+ assertNull(observer.mContextMenu); |
+ final View view = ThreadUtils.runOnUiThreadBlocking(new Callable<View>() { |
+ @Override |
+ public View call() throws Exception { |
+ return activity.findViewById(android.R.id.content).getRootView(); |
+ } |
+ }); |
+ TouchCommon.longPressView(view, view.getWidth() / 2, view.getHeight() / 2); |
+ assertTrue(CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ return observer.mContextMenu != null; |
+ } |
+ })); |
+ |
+ activity.getActivityTab().removeObserver(observer); |
+ |
+ // We expect tab to open in the background, i.e. tab index / id should |
+ // stay the same. |
+ final DocumentTabModelSelector selector = |
+ ChromeMobileApplication.getDocumentTabModelSelector(); |
+ final TabModel tabModel = selector.getModel(false); |
+ final int expectedTabCount = tabModel.getCount() + 1; |
+ final int expectedTabIndex = tabModel.index(); |
+ final int expectedTabId = selector.getCurrentTabId(); |
+ |
+ // Select the "open in new tab" option to open a tab in the background. |
+ ActivityUtils.waitForActivity(getInstrumentation(), DocumentActivity.class, |
+ new Runnable() { |
+ @Override |
+ public void run() { |
+ assertTrue(observer.mContextMenu.performIdentifierAction( |
+ R.id.contextmenu_open_in_new_tab, 0)); |
+ } |
+ } |
+ ); |
+ |
+ assertTrue(CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ if (expectedTabCount != tabModel.getCount()) return false; |
+ if (expectedTabIndex != tabModel.index()) return false; |
+ if (expectedTabId != selector.getCurrentTabId()) return false; |
+ return true; |
+ } |
+ })); |
+ |
+ assertEquals(activity, ApplicationStatus.getLastTrackedFocusedActivity()); |
+ } |
+ |
+ /** |
+ * Returns the number of currently running renderer services. |
+ */ |
+ private int countRenderers() { |
+ ActivityManager activityManager = (ActivityManager) mContext.getSystemService( |
+ Context.ACTIVITY_SERVICE); |
+ |
+ int rendererCount = 0; |
+ List<RunningServiceInfo> serviceInfos = activityManager.getRunningServices( |
+ Integer.MAX_VALUE); |
+ for (RunningServiceInfo serviceInfo : serviceInfos) { |
+ if (serviceInfo.service.getClassName().startsWith( |
+ SandboxedProcessService.class.getName())) { |
+ rendererCount++; |
+ } |
+ } |
+ |
+ return rendererCount; |
+ } |
+} |