Index: chrome/android/javatests/src/org/chromium/chrome/browser/TabsTest.java |
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/TabsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/TabsTest.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..00c577d7e96cec28d5f385fe4c496ba54e6d8ef6 |
--- /dev/null |
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/TabsTest.java |
@@ -0,0 +1,1614 @@ |
+// 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; |
+ |
+import static org.chromium.base.test.util.Restriction.RESTRICTION_TYPE_NON_LOW_END_DEVICE; |
+import static org.chromium.base.test.util.Restriction.RESTRICTION_TYPE_PHONE; |
+ |
+import android.content.DialogInterface; |
+import android.content.pm.ActivityInfo; |
+import android.os.Debug; |
+import android.os.SystemClock; |
+import android.test.FlakyTest; |
+import android.test.suitebuilder.annotation.LargeTest; |
+import android.test.suitebuilder.annotation.MediumTest; |
+import android.test.suitebuilder.annotation.SmallTest; |
+import android.test.suitebuilder.annotation.Smoke; |
+import android.util.Log; |
+import android.view.View; |
+ |
+import com.google.android.apps.chrome.R; |
+ |
+import junit.framework.Assert; |
+ |
+import org.chromium.base.CommandLine; |
+import org.chromium.base.ThreadUtils; |
+import org.chromium.base.annotations.SuppressFBWarnings; |
+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.UrlUtils; |
+import org.chromium.chrome.browser.compositor.CompositorViewHolder; |
+import org.chromium.chrome.browser.compositor.layouts.Layout; |
+import org.chromium.chrome.browser.compositor.layouts.LayoutManager; |
+import org.chromium.chrome.browser.compositor.layouts.LayoutManagerChrome; |
+import org.chromium.chrome.browser.compositor.layouts.LayoutManagerChromePhone; |
+import org.chromium.chrome.browser.compositor.layouts.StaticLayout; |
+import org.chromium.chrome.browser.compositor.layouts.components.LayoutTab; |
+import org.chromium.chrome.browser.compositor.layouts.eventfilter.EdgeSwipeEventFilter.ScrollDirection; |
+import org.chromium.chrome.browser.compositor.layouts.eventfilter.EdgeSwipeHandler; |
+import org.chromium.chrome.browser.compositor.layouts.phone.StackLayout; |
+import org.chromium.chrome.browser.compositor.layouts.phone.stack.Stack; |
+import org.chromium.chrome.browser.compositor.layouts.phone.stack.StackTab; |
+import org.chromium.chrome.browser.tabmodel.EmptyTabModelObserver; |
+import org.chromium.chrome.browser.tabmodel.EmptyTabModelSelectorObserver; |
+import org.chromium.chrome.browser.tabmodel.TabModel; |
+import org.chromium.chrome.browser.tabmodel.TabModel.TabSelectionType; |
+import org.chromium.chrome.browser.tabmodel.TabModelObserver; |
+import org.chromium.chrome.browser.tabmodel.TabModelSelector; |
+import org.chromium.chrome.browser.tabmodel.TabModelSelectorImpl; |
+import org.chromium.chrome.browser.tabmodel.TabModelUtils; |
+import org.chromium.chrome.browser.toolbar.ToolbarPhone; |
+import org.chromium.chrome.test.ChromeTabbedActivityTestBase; |
+import org.chromium.chrome.test.util.ChromeTabUtils; |
+import org.chromium.chrome.test.util.MenuUtils; |
+import org.chromium.chrome.test.util.OverviewModeBehaviorWatcher; |
+import org.chromium.chrome.test.util.TestHttpServerClient; |
+import org.chromium.content.browser.ContentViewCore; |
+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.JavaScriptUtils; |
+import org.chromium.content.browser.test.util.UiUtils; |
+import org.chromium.content.common.ContentSwitches; |
+import org.chromium.content_public.browser.WebContents; |
+import org.chromium.content_public.browser.WebContentsObserver; |
+ |
+import java.util.concurrent.Callable; |
+import java.util.concurrent.TimeUnit; |
+import java.util.concurrent.TimeoutException; |
+import java.util.concurrent.atomic.AtomicBoolean; |
+import java.util.concurrent.atomic.AtomicReference; |
+ |
+/** |
+ * General Tab tests. |
+ */ |
+public class TabsTest extends ChromeTabbedActivityTestBase { |
+ |
+ private static final String TEST_FILE_PATH = "chrome/test/data/android/tabstest/tabs_test.html"; |
+ private static final String TEST_PAGE_FILE_PATH = "chrome/test/data/google/google.html"; |
+ |
+ private float mPxToDp = 1.0f; |
+ private float mTabsViewHeightDp; |
+ private float mTabsViewWidthDp; |
+ |
+ private boolean mNotifyChangedCalled; |
+ |
+ private static final int SWIPE_TO_RIGHT_DIRECTION = 1; |
+ private static final int SWIPE_TO_LEFT_DIRECTION = -1; |
+ |
+ private static final long WAIT_RESIZE_TIMEOUT_MS = 3000; |
+ |
+ private static final int STRESSFUL_TAB_COUNT = 100; |
+ |
+ private static final String RESIZE_TEST_URL = UrlUtils.encodeHtmlDataUri( |
+ "<html><head><script>" |
+ + " var resizeHappened = false;" |
+ + " function onResize() {" |
+ + " resizeHappened = true;" |
+ + " document.getElementById('test').textContent =" |
+ + " window.innerWidth + 'x' + window.innerHeight;" |
+ + " }" |
+ + "</script></head>" |
+ + "<body onresize=\"onResize()\">" |
+ + " <div id=\"test\">No resize event has been received yet.</div>" |
+ + "</body></html>"); |
+ |
+ /** |
+ * Verify that spawning a popup from a background tab in a different model works properly. |
+ * @throws InterruptedException |
+ * @throws TimeoutException |
+ */ |
+ @LargeTest |
+ @Feature({"Navigation"}) |
+ @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) |
+ public void testSpawnPopupOnBackgroundTab() throws InterruptedException, TimeoutException { |
+ loadUrl(UrlUtils.getIsolatedTestFileUrl(TEST_FILE_PATH)); |
+ final Tab tab = getActivity().getActivityTab(); |
+ |
+ newIncognitoTabFromMenu(); |
+ |
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
+ @Override |
+ public void run() { |
+ tab.getWebContents().evaluateJavaScript( |
+ "(function() {" |
+ + " window.open('www.google.com');" |
+ + "})()", |
+ null); |
+ } |
+ }); |
+ |
+ assertTrue("Tab never spawned in normal model.", |
+ CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ return getActivity().getTabModelSelector().getModel(false).getCount() == 2; |
+ } |
+ })); |
+ } |
+ |
+ @MediumTest |
+ public void testAlertDialogDoesNotChangeActiveModel() throws InterruptedException { |
+ newIncognitoTabFromMenu(); |
+ loadUrl(UrlUtils.getIsolatedTestFileUrl(TEST_FILE_PATH)); |
+ final Tab tab = getActivity().getActivityTab(); |
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
+ @Override |
+ public void run() { |
+ tab.getWebContents().evaluateJavaScript( |
+ "(function() {" |
+ + " alert('hi');" |
+ + "})()", |
+ null); |
+ } |
+ }); |
+ |
+ final AtomicReference<JavascriptAppModalDialog> dialog = |
+ new AtomicReference<JavascriptAppModalDialog>(); |
+ |
+ CriteriaHelper.pollForCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ dialog.set(JavascriptAppModalDialog.getCurrentDialogForTest()); |
+ |
+ return dialog.get() != null; |
+ } |
+ }); |
+ |
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
+ @Override |
+ public void run() { |
+ dialog.get().onClick(null, DialogInterface.BUTTON_POSITIVE); |
+ } |
+ }); |
+ |
+ dialog.set(null); |
+ |
+ CriteriaHelper.pollForCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ return JavascriptAppModalDialog.getCurrentDialogForTest() == null; |
+ } |
+ }); |
+ |
+ assertTrue("Incognito model was not selected", |
+ getActivity().getTabModelSelector().isIncognitoSelected()); |
+ } |
+ |
+ /** |
+ * Verify New Tab Open and Close Event not from the context menu. |
+ * @throws InterruptedException |
+ * |
+ * https://crbug.com/490473 |
+ * @LargeTest |
+ * @Feature({"Android-TabSwitcher"}) |
+ * @Restriction(RESTRICTION_TYPE_PHONE) |
+ */ |
+ @DisabledTest |
+ public void testOpenAndCloseNewTabButton() throws InterruptedException { |
+ startMainActivityWithURL(UrlUtils.getIsolatedTestFileUrl(TEST_FILE_PATH)); |
+ getInstrumentation().runOnMainSync(new Runnable() { |
+ @Override |
+ public void run() { |
+ String title = getActivity().getCurrentTabModel().getTabAt(0).getTitle(); |
+ assertEquals("Data file for TabsTest", title); |
+ } |
+ }); |
+ final int tabCount = getActivity().getCurrentTabModel().getCount(); |
+ OverviewModeBehaviorWatcher overviewModeWatcher = new OverviewModeBehaviorWatcher( |
+ getActivity().getLayoutManager(), true, false); |
+ View tabSwitcherButton = getActivity().findViewById(R.id.tab_switcher_button); |
+ assertNotNull("'tab_switcher_button' view is not found", tabSwitcherButton); |
+ singleClickView(tabSwitcherButton, tabSwitcherButton.getWidth() / 2, |
+ tabSwitcherButton.getHeight() / 2); |
+ overviewModeWatcher.waitForBehavior(); |
+ overviewModeWatcher = new OverviewModeBehaviorWatcher( |
+ getActivity().getLayoutManager(), false, true); |
+ View newTabButton = getActivity().findViewById(R.id.new_tab_button); |
+ assertNotNull("'new_tab_button' view is not found", newTabButton); |
+ singleClickView(newTabButton, newTabButton.getWidth() / 2, newTabButton.getHeight() / 2); |
+ overviewModeWatcher.waitForBehavior(); |
+ |
+ getInstrumentation().runOnMainSync(new Runnable() { |
+ @Override |
+ public void run() { |
+ assertEquals("The tab count is wrong", |
+ tabCount + 1, getActivity().getCurrentTabModel().getCount()); |
+ } |
+ }); |
+ |
+ assertTrue(CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ Tab tab = getActivity().getCurrentTabModel().getTabAt(1); |
+ String title = tab.getTitle().toLowerCase(); |
+ String expectedTitle = "new tab"; |
+ return title.startsWith(expectedTitle); |
+ } |
+ })); |
+ |
+ ChromeTabUtils.closeCurrentTab(getInstrumentation(), getActivity()); |
+ getInstrumentation().runOnMainSync(new Runnable() { |
+ @Override |
+ public void run() { |
+ assertEquals(tabCount, getActivity().getCurrentTabModel().getCount()); |
+ } |
+ }); |
+ } |
+ |
+ static class SimulateClickOnMainThread implements Runnable { |
+ private final LayoutManagerChrome mLayoutManager; |
+ private final float mX; |
+ private final float mY; |
+ |
+ public SimulateClickOnMainThread(LayoutManagerChrome layoutManager, float x, float y) { |
+ mLayoutManager = layoutManager; |
+ mX = x; |
+ mY = y; |
+ } |
+ |
+ @Override |
+ public void run() { |
+ mLayoutManager.simulateClick(mX, mY); |
+ } |
+ } |
+ |
+ static class SimulateTabSwipeOnMainThread implements Runnable { |
+ private final LayoutManagerChrome mLayoutManager; |
+ private final float mX; |
+ private final float mY; |
+ private final float mDeltaX; |
+ private final float mDeltaY; |
+ |
+ public SimulateTabSwipeOnMainThread(LayoutManagerChrome layoutManager, float x, float y, |
+ float dX, float dY) { |
+ mLayoutManager = layoutManager; |
+ mX = x; |
+ mY = y; |
+ mDeltaX = dX; |
+ mDeltaY = dY; |
+ } |
+ |
+ @Override |
+ public void run() { |
+ mLayoutManager.simulateDrag(mX, mY, mDeltaX, mDeltaY); |
+ } |
+ } |
+ |
+ /** |
+ * Verify that the provided click position closes a tab. |
+ * @throws InterruptedException |
+ */ |
+ private void checkCloseTabAtPosition(final float x, final float y) |
+ throws InterruptedException { |
+ getActivity(); |
+ |
+ int initialTabCount = getActivity().getCurrentTabModel().getCount(); |
+ ChromeTabUtils.newTabFromMenu(getInstrumentation(), getActivity()); |
+ getInstrumentation().waitForIdleSync(); |
+ View button = getActivity().findViewById(R.id.tab_switcher_button); |
+ OverviewModeBehaviorWatcher overviewModeWatcher = new OverviewModeBehaviorWatcher( |
+ getActivity().getLayoutManager(), true, false); |
+ singleClickView(button, button.getWidth() / 2, button.getHeight() / 2); |
+ overviewModeWatcher.waitForBehavior(); |
+ assertTrue("Expected: " + (initialTabCount + 1) + " tab Got: " |
+ + getActivity().getCurrentTabModel().getCount(), |
+ (initialTabCount + 1) == getActivity().getCurrentTabModel().getCount()); |
+ getInstrumentation().waitForIdleSync(); |
+ final LayoutManagerChrome layoutManager = updateTabsViewSize(); |
+ ChromeTabUtils.closeTabWithAction(getInstrumentation(), getActivity(), new Runnable() { |
+ @Override |
+ public void run() { |
+ getInstrumentation().runOnMainSync( |
+ new SimulateClickOnMainThread(layoutManager, x, y)); |
+ } |
+ }); |
+ assertTrue("Expected: " + initialTabCount + " tab Got: " |
+ + getActivity().getCurrentTabModel().getCount(), |
+ initialTabCount == getActivity().getCurrentTabModel().getCount()); |
+ } |
+ |
+ /** |
+ * Verify close button works in the TabSwitcher in portrait mode. |
+ * This code does not handle properly different screen densities. |
+ * @throws InterruptedException |
+ */ |
+ @LargeTest |
+ @Restriction({RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE}) |
+ @Feature({"Android-TabSwitcher"}) |
+ public void testTabSwitcherPortraitCloseButton() throws InterruptedException { |
+ getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); |
+ int portraitWidth = Math.min(getActivity().getResources().getDisplayMetrics().widthPixels, |
+ getActivity().getResources().getDisplayMetrics().heightPixels); |
+ // Hard-coded coordinates of the close button on the top right of the screen. |
+ // If the coordinates need to be updated, the easiest is to take a screenshot and measure. |
+ // Note that starting from the right of the screen should cover any screen size. |
+ checkCloseTabAtPosition(portraitWidth * mPxToDp - 32, 70); |
+ } |
+ |
+ /** |
+ * Verify close button works in the TabSwitcher in landscape mode. |
+ * This code does not handle properly different screen densities. |
+ * @throws InterruptedException |
+ * Bug: crbug.com/170179 |
+ * @Restriction({RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE}) |
+ * @LargeTest |
+ * @Feature({"Android-TabSwitcher"}) |
+ */ |
+ @FlakyTest |
+ public void testTabSwitcherLandscapeCloseButton() throws InterruptedException { |
+ getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); |
+ // Hard-coded coordinates of the close button on the bottom left of the screen. |
+ // If the coordinates need to be updated, the easiest is to take a screenshot and measure. |
+ checkCloseTabAtPosition(31 * mPxToDp, 31 * mPxToDp); |
+ } |
+ |
+ /** |
+ * Verify that we can open a large number of tabs without running out of |
+ * memory. This test waits for the NTP to load before opening the next one. |
+ * This is a LargeTest but because we're doing it "slowly", we need to further scale |
+ * the timeout for adb am instrument and the various events. |
+ */ |
+ /* |
+ * @EnormousTest |
+ * @TimeoutScale(10) |
+ * @Feature({"Android-TabSwitcher"}) |
+ * Bug crbug.com/166208 |
+ */ |
+ @DisabledTest |
+ public void testOpenManyTabsSlowly() throws InterruptedException { |
+ int startCount = getActivity().getCurrentTabModel().getCount(); |
+ for (int i = 1; i <= STRESSFUL_TAB_COUNT; ++i) { |
+ ChromeTabUtils.newTabFromMenu(getInstrumentation(), getActivity()); |
+ getInstrumentation().waitForIdleSync(); |
+ assertEquals(startCount + i, getActivity().getCurrentTabModel().getCount()); |
+ } |
+ } |
+ |
+ /** |
+ * Verify that we can open a large number of tabs without running out of |
+ * memory. This test hammers the "new tab" button quickly to stress the app. |
+ * |
+ * @LargeTest |
+ * @TimeoutScale(10) |
+ * @Feature({"Android-TabSwitcher"}) |
+ * |
+ */ |
+ @FlakyTest |
+ public void testOpenManyTabsQuickly() { |
+ int startCount = getActivity().getCurrentTabModel().getCount(); |
+ for (int i = 1; i <= STRESSFUL_TAB_COUNT; ++i) { |
+ MenuUtils.invokeCustomMenuActionSync(getInstrumentation(), getActivity(), |
+ R.id.new_tab_menu_id); |
+ assertEquals(startCount + i, getActivity().getCurrentTabModel().getCount()); |
+ } |
+ } |
+ |
+ /** |
+ * Verify that we can open a burst of new tabs, even when there are already |
+ * a large number of tabs open. |
+ * Bug: crbug.com/180718 |
+ * @EnormousTest |
+ * @TimeoutScale(30) |
+ * @Feature({"Navigation"}) |
+ */ |
+ @FlakyTest |
+ public void testOpenManyTabsInBursts() throws InterruptedException { |
+ final int burstSize = 5; |
+ final String url = TestHttpServerClient.getUrl(TEST_PAGE_FILE_PATH); |
+ final int startCount = getActivity().getCurrentTabModel().getCount(); |
+ for (int tabCount = startCount; tabCount < STRESSFUL_TAB_COUNT; tabCount += burstSize) { |
+ loadUrlInManyNewTabs(url, burstSize); |
+ assertEquals(tabCount + burstSize, getActivity().getCurrentTabModel().getCount()); |
+ } |
+ } |
+ |
+ /** |
+ * Verify opening 10 tabs at once and that each tab loads when selected. |
+ */ |
+ /* |
+ * @EnormousTest |
+ * @TimeoutScale(30) |
+ * @Feature({"Navigation"}) |
+ * crbug/223110 |
+ */ |
+ @FlakyTest |
+ public void testOpenManyTabsAtOnce10() throws InterruptedException { |
+ openAndVerifyManyTestTabs(10); |
+ } |
+ |
+ /** |
+ * Verify that we can open a large number of tabs all at once and that each |
+ * tab loads when selected. |
+ */ |
+ private void openAndVerifyManyTestTabs(final int num) throws InterruptedException { |
+ final String url = TestHttpServerClient.getUrl(TEST_PAGE_FILE_PATH); |
+ int startCount = getActivity().getCurrentTabModel().getCount(); |
+ loadUrlInManyNewTabs(url, num); |
+ assertEquals(startCount + num, |
+ getActivity().getCurrentTabModel().getCount()); |
+ } |
+ |
+ class ClickOptionButtonOnMainThread implements Runnable { |
+ @Override |
+ public void run() { |
+ // This is equivalent to clickById(R.id.tab_switcher_button) but does not rely on the |
+ // event pipeline. |
+ View button = getActivity().findViewById(R.id.tab_switcher_button); |
+ assertNotNull("Could not find view R.id.tab_switcher_button", button); |
+ View toolbar = getActivity().findViewById(R.id.toolbar); |
+ assertNotNull("Could not find view R.id.toolbar", toolbar); |
+ assertTrue("R.id.toolbar is not a ToolbarPhone", toolbar instanceof ToolbarPhone); |
+ ((ToolbarPhone) toolbar).onClick(button); |
+ } |
+ } |
+ |
+ /** |
+ * Displays the tabSwitcher mode and wait for it to settle. |
+ */ |
+ private void showOverviewAndWaitForAnimation() throws InterruptedException { |
+ OverviewModeBehaviorWatcher overviewModeWatcher = new OverviewModeBehaviorWatcher( |
+ getActivity().getLayoutManager(), true, false); |
+ // For some unknown reasons calling clickById(R.id.tab_switcher_button) sometimes hang. |
+ // The following is verbose but more reliable. |
+ getInstrumentation().runOnMainSync(new ClickOptionButtonOnMainThread()); |
+ overviewModeWatcher.waitForBehavior(); |
+ } |
+ |
+ /** |
+ * Exits the tabSwitcher mode and wait for it to settle. |
+ */ |
+ private void hideOverviewAndWaitForAnimation() throws InterruptedException { |
+ OverviewModeBehaviorWatcher overviewModeWatcher = new OverviewModeBehaviorWatcher( |
+ getActivity().getLayoutManager(), false, true); |
+ getInstrumentation().runOnMainSync(new ClickOptionButtonOnMainThread()); |
+ overviewModeWatcher.waitForBehavior(); |
+ } |
+ |
+ /** |
+ * Opens tabs to populate the model to a given count. |
+ * @param targetTabCount The desired number of tabs in the model. |
+ * @param waitToLoad Whether the tabs need to be fully loaded. |
+ * @return The new number of tabs in the model. |
+ * @throws InterruptedException |
+ */ |
+ private int openTabs(final int targetTabCount, boolean waitToLoad) throws InterruptedException { |
+ int tabCount = getActivity().getCurrentTabModel().getCount(); |
+ while (tabCount < targetTabCount) { |
+ ChromeTabUtils.newTabFromMenu(getInstrumentation(), getActivity()); |
+ tabCount++; |
+ assertEquals("The tab count is wrong", |
+ tabCount, getActivity().getCurrentTabModel().getCount()); |
+ Tab tab = TabModelUtils.getCurrentTab(getActivity().getCurrentTabModel()); |
+ while (waitToLoad && tab.isLoading()) { |
+ Thread.yield(); |
+ } |
+ } |
+ return tabCount; |
+ } |
+ |
+ /** |
+ * Verifies that when more than 9 tabs are open only at most 8 are drawn. Basically it verifies |
+ * that the tab culling mechanism works properly. |
+ */ |
+ /* |
+ @LargeTest |
+ @Feature({"Android-TabSwitcher"}) |
+ Bug http://crbug.com/156746 |
+ */ |
+ @DisabledTest |
+ @Restriction({RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE}) |
+ public void testTabsCulling() throws InterruptedException { |
+ // Open one more tabs than maxTabsDrawn. |
+ final int maxTabsDrawn = 8; |
+ int tabCount = openTabs(maxTabsDrawn + 1, false); |
+ showOverviewAndWaitForAnimation(); |
+ |
+ // Check counts. |
+ LayoutManagerChromePhone layoutManager = |
+ (LayoutManagerChromePhone) getActivity().getLayoutManager(); |
+ int drawnCount = layoutManager.getOverviewLayout().getLayoutTabsDrawnCount(); |
+ int drawnExpected = Math.min(tabCount, maxTabsDrawn); |
+ assertEquals("The number of drawn tab is wrong", drawnExpected, drawnCount); |
+ } |
+ |
+ /** |
+ * Checks the stacked tabs in the stack are visible. |
+ * @throws InterruptedException |
+ */ |
+ private void checkTabsStacking() throws InterruptedException { |
+ final int count = getActivity().getCurrentTabModel().getCount(); |
+ assertEquals("The number of tab in the stack should match the number of tabs in the model", |
+ count, getLayoutTabInStackCount(false)); |
+ |
+ assertTrue("The selected tab should always be visible", |
+ stackTabIsVisible(false, getActivity().getCurrentTabModel().index())); |
+ for (int i = 0; i < Stack.MAX_NUMBER_OF_STACKED_TABS_TOP && i < count; i++) { |
+ assertTrue("The stacked tab " + i + " from the top should always be visible", |
+ stackTabIsVisible(false, i)); |
+ } |
+ for (int i = 0; i < Stack.MAX_NUMBER_OF_STACKED_TABS_BOTTOM && i < count; i++) { |
+ assertTrue("The stacked tab " + i + " from the bottom should always be visible", |
+ stackTabIsVisible(false, count - 1 - i)); |
+ } |
+ } |
+ |
+ /** |
+ * Verifies that the tab are actually stacking at the bottom and top of the screen. |
+ */ |
+ /** |
+ * Bug: crbug.com/170179 |
+ * @LargeTest |
+ * @Feature({"Android-TabSwitcher"}) |
+ */ |
+ @FlakyTest |
+ @Restriction({RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE}) |
+ public void testTabsStacking() throws InterruptedException { |
+ final int count = openTabs(12, false); |
+ |
+ // Selecting the first tab to scroll all the way to the top. |
+ getInstrumentation().runOnMainSync(new Runnable() { |
+ @Override |
+ public void run() { |
+ TabModelUtils.setIndex(getActivity().getCurrentTabModel(), 0); |
+ } |
+ }); |
+ showOverviewAndWaitForAnimation(); |
+ checkTabsStacking(); |
+ |
+ // Selecting the last tab to scroll all the way to the bottom. |
+ hideOverviewAndWaitForAnimation(); |
+ getInstrumentation().runOnMainSync(new Runnable() { |
+ @Override |
+ public void run() { |
+ TabModelUtils.setIndex(getActivity().getCurrentTabModel(), count - 1); |
+ } |
+ }); |
+ showOverviewAndWaitForAnimation(); |
+ checkTabsStacking(); |
+ } |
+ |
+ /** |
+ * @return A stable read of allocated size (native + dalvik) after gc. |
+ */ |
+ @SuppressFBWarnings("DM_GC") |
+ private long getStableAllocatedSize() { |
+ // Measure the equivalent of allocated size native + dalvik in: |
+ // adb shell dumpsys meminfo | grep chrome -A 20 |
+ int maxTries = 8; |
+ int tries = 0; |
+ long threshold = 512; // bytes |
+ long lastAllocatedSize = Long.MAX_VALUE; |
+ long currentAllocatedSize = 0; |
+ while (tries < maxTries && Math.abs(currentAllocatedSize - lastAllocatedSize) > threshold) { |
+ System.gc(); |
+ try { |
+ Thread.sleep(1000 + tries * 500); // Memory measurement is not an exact science... |
+ lastAllocatedSize = currentAllocatedSize; |
+ currentAllocatedSize = Debug.getNativeHeapAllocatedSize() |
+ + Runtime.getRuntime().totalMemory(); |
+ //Log.w("MEMORY_MEASURE", "[" + tries + "/" + maxTries + "]" + |
+ // "currentAllocatedSize " + currentAllocatedSize); |
+ } catch (InterruptedException e) { |
+ e.printStackTrace(); |
+ } |
+ tries++; |
+ } |
+ assertTrue("Could not have a stable read on native allocated size even after " |
+ + tries + " gc.", tries < maxTries); |
+ return currentAllocatedSize; |
+ } |
+ |
+ /** |
+ * Verify that switching back and forth to the tabswitcher does not leak memory. |
+ */ |
+ /** |
+ * Bug: crbug.com/303319 |
+ * @LargeTest |
+ * @Feature({"Android-TabSwitcher"}) |
+ */ |
+ @FlakyTest |
+ @Restriction(RESTRICTION_TYPE_PHONE) |
+ public void testTabSwitcherMemoryLeak() throws InterruptedException { |
+ openTabs(4, true); |
+ |
+ int maxTries = 10; |
+ int tries = 0; |
+ long threshold = 1024; // bytes |
+ long lastAllocatedSize = 0; |
+ long currentAllocatedSize = 2 * threshold; |
+ while (tries < maxTries && (lastAllocatedSize + threshold) < currentAllocatedSize) { |
+ showOverviewAndWaitForAnimation(); |
+ |
+ lastAllocatedSize = currentAllocatedSize; |
+ currentAllocatedSize = getStableAllocatedSize(); |
+ //Log.w("MEMORY_TEST", "[" + tries + "/" + maxTries + "]" + |
+ // "currentAllocatedSize " + currentAllocatedSize); |
+ |
+ hideOverviewAndWaitForAnimation(); |
+ tries++; |
+ } |
+ |
+ assertTrue("Native heap allocated size keeps increasing even after " |
+ + tries + " iterations", tries < maxTries); |
+ } |
+ |
+ /** |
+ * Verify that switching back and forth stay stable. This test last for at least 8 seconds. |
+ */ |
+ @LargeTest |
+ @Restriction(RESTRICTION_TYPE_PHONE) |
+ @Feature({"Android-TabSwitcher"}) |
+ public void testTabSwitcherStability() throws InterruptedException { |
+ openTabs(8, true); |
+ |
+ // This is about as fast as you can ever click. |
+ final long fastestUserInput = 20; // ms |
+ for (int i = 0; i < 200; i++) { |
+ // Show overview |
+ getInstrumentation().runOnMainSync(new ClickOptionButtonOnMainThread()); |
+ Thread.sleep(fastestUserInput); |
+ |
+ // hide overview |
+ getInstrumentation().runOnMainSync(new ClickOptionButtonOnMainThread()); |
+ Thread.sleep(fastestUserInput); |
+ } |
+ } |
+ |
+ @LargeTest |
+ @Feature({"Android-TabSwitcher"}) |
+ @Restriction({RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE}) |
+ public void testTabSelectionPortrait() throws InterruptedException { |
+ getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); |
+ checkTabSelection(2, 0, false); |
+ |
+ // Ensure all tabs following the selected tab are off the screen when the animation is |
+ // complete. |
+ final int count = getLayoutTabInStackCount(false); |
+ for (int i = 1; i < count; i++) { |
+ float y = getLayoutTabInStackXY(false, i)[1]; |
+ assertTrue( |
+ String.format("Tab %d's final draw Y, %f, should exceed the view height, %f.", |
+ i, y, mTabsViewHeightDp), |
+ y >= mTabsViewHeightDp); |
+ } |
+ } |
+ |
+ /** |
+ * Bug: crbug.com/170179 |
+ * @LargeTest |
+ * @Feature({"Android-TabSwitcher"}) |
+ */ |
+ @FlakyTest |
+ @Restriction({RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE}) |
+ public void testTabSelectionLandscape() throws InterruptedException { |
+ getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); |
+ checkTabSelection(2, 0, true); |
+ |
+ // Ensure all tabs following the selected tab are off the screen when the animation is |
+ // complete. |
+ final int count = getLayoutTabInStackCount(false); |
+ for (int i = 1; i < count; i++) { |
+ float x = getLayoutTabInStackXY(false, i)[0]; |
+ assertTrue( |
+ String.format("Tab %d's final draw X, %f, should exceed the view width, %f.", |
+ i, x, mTabsViewWidthDp), |
+ x >= mTabsViewWidthDp); |
+ } |
+ } |
+ |
+ /** |
+ * Verify that we don't crash and show the overview mode after closing the last tab. |
+ */ |
+ @SmallTest |
+ @Restriction(RESTRICTION_TYPE_PHONE) |
+ @Feature({"Android-TabSwitcher"}) |
+ public void testCloseLastTabFromMain() throws InterruptedException { |
+ OverviewModeBehaviorWatcher overviewModeWatcher = new OverviewModeBehaviorWatcher( |
+ getActivity().getLayoutManager(), true, false); |
+ ChromeTabUtils.closeCurrentTab(getInstrumentation(), getActivity()); |
+ getInstrumentation().waitForIdleSync(); |
+ overviewModeWatcher.waitForBehavior(); |
+ } |
+ |
+ private LayoutManagerChrome updateTabsViewSize() { |
+ View tabsView = getActivity().getTabsView(); |
+ mTabsViewHeightDp = tabsView.getHeight() * mPxToDp; |
+ mTabsViewWidthDp = tabsView.getWidth() * mPxToDp; |
+ return getActivity().getLayoutManager(); |
+ } |
+ |
+ private Stack getStack(final LayoutManagerChrome layoutManager, boolean isIncognito) { |
+ assertTrue("getStack must be executed on the ui thread", |
+ ThreadUtils.runningOnUiThread()); |
+ LayoutManagerChromePhone layoutManagerPhone = (LayoutManagerChromePhone) layoutManager; |
+ StackLayout layout = (StackLayout) layoutManagerPhone.getOverviewLayout(); |
+ return (layout).getTabStack(isIncognito); |
+ } |
+ |
+ private int getLayoutTabInStackCount(final boolean isIncognito) { |
+ final LayoutManagerChrome layoutManager = updateTabsViewSize(); |
+ final int[] count = new int[1]; |
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
+ @Override |
+ public void run() { |
+ Stack stack = getStack(layoutManager, isIncognito); |
+ count[0] = stack.getTabs().length; |
+ } |
+ }); |
+ return count[0]; |
+ } |
+ |
+ private boolean stackTabIsVisible(final boolean isIncognito, final int index) { |
+ final LayoutManagerChrome layoutManager = updateTabsViewSize(); |
+ final boolean[] isVisible = new boolean[1]; |
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
+ @Override |
+ public void run() { |
+ Stack stack = getStack(layoutManager, isIncognito); |
+ isVisible[0] = (stack.getTabs())[index].getLayoutTab().isVisible(); |
+ } |
+ }); |
+ return isVisible[0]; |
+ } |
+ |
+ private float[] getLayoutTabInStackXY(final boolean isIncognito, final int index) { |
+ final LayoutManagerChrome layoutManager = updateTabsViewSize(); |
+ final float[] xy = new float[2]; |
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
+ @Override |
+ public void run() { |
+ Stack stack = getStack(layoutManager, isIncognito); |
+ xy[0] = (stack.getTabs())[index].getLayoutTab().getX(); |
+ xy[1] = (stack.getTabs())[index].getLayoutTab().getY(); |
+ } |
+ }); |
+ return xy; |
+ } |
+ |
+ private float[] getStackTabClickTarget(final int tabIndexToSelect, final boolean isIncognito, |
+ final boolean isLandscape) { |
+ final LayoutManagerChrome layoutManager = updateTabsViewSize(); |
+ final float[] target = new float[2]; |
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
+ @Override |
+ public void run() { |
+ Stack stack = getStack(layoutManager, isIncognito); |
+ StackTab[] tabs = stack.getTabs(); |
+ // The position of the click is expressed from the top left corner of the content. |
+ // The aim is to find an offset that is inside the content but not on the close |
+ // button. For this, we calculate the center of the visible tab area. |
+ LayoutTab layoutTab = tabs[tabIndexToSelect].getLayoutTab(); |
+ LayoutTab nextLayoutTab = (tabIndexToSelect + 1) < tabs.length |
+ ? tabs[tabIndexToSelect + 1].getLayoutTab() : null; |
+ |
+ float tabOffsetX = layoutTab.getX(); |
+ float tabOffsetY = layoutTab.getY(); |
+ float tabRightX, tabBottomY; |
+ if (isLandscape) { |
+ tabRightX = nextLayoutTab != null |
+ ? nextLayoutTab.getX() |
+ : tabOffsetX + layoutTab.getScaledContentWidth(); |
+ tabBottomY = tabOffsetY + layoutTab.getScaledContentHeight(); |
+ } else { |
+ tabRightX = tabOffsetX + layoutTab.getScaledContentWidth(); |
+ tabBottomY = nextLayoutTab != null |
+ ? nextLayoutTab.getY() |
+ : tabOffsetY + layoutTab.getScaledContentHeight(); |
+ } |
+ tabRightX = Math.min(tabRightX, mTabsViewWidthDp); |
+ tabBottomY = Math.min(tabBottomY, mTabsViewHeightDp); |
+ |
+ target[0] = (tabOffsetX + tabRightX) / 2.0f; |
+ target[1] = (tabOffsetY + tabBottomY) / 2.0f; |
+ } |
+ }); |
+ return target; |
+ } |
+ |
+ private void checkTabSelection(int additionalTabsToOpen, int tabIndexToSelect, |
+ boolean isLandscape) throws InterruptedException { |
+ for (int i = 0; i < additionalTabsToOpen; i++) { |
+ ChromeTabUtils.newTabFromMenu(getInstrumentation(), getActivity()); |
+ } |
+ assertEquals("Number of open tabs does not match", |
+ additionalTabsToOpen + 1 , getActivity().getCurrentTabModel().getCount()); |
+ showOverviewAndWaitForAnimation(); |
+ |
+ float[] coordinates = getStackTabClickTarget(tabIndexToSelect, false, isLandscape); |
+ float clickX = coordinates[0]; |
+ float clickY = coordinates[1]; |
+ |
+ OverviewModeBehaviorWatcher overviewModeWatcher = new OverviewModeBehaviorWatcher( |
+ getActivity().getLayoutManager(), false, true); |
+ |
+ final LayoutManagerChrome layoutManager = updateTabsViewSize(); |
+ getInstrumentation().runOnMainSync(new SimulateClickOnMainThread(layoutManager, |
+ (int) clickX, (int) clickY)); |
+ overviewModeWatcher.waitForBehavior(); |
+ |
+ // Make sure we did not accidentally close a tab. |
+ assertEquals("Number of open tabs does not match", |
+ additionalTabsToOpen + 1 , getActivity().getCurrentTabModel().getCount()); |
+ } |
+ |
+ public void swipeToCloseTab(final int tabIndexToClose, final boolean isLandscape, |
+ final boolean isIncognito, final int swipeDirection) throws InterruptedException { |
+ final LayoutManagerChrome layoutManager = updateTabsViewSize(); |
+ float[] coordinates = getStackTabClickTarget(tabIndexToClose, isIncognito, isLandscape); |
+ final float clickX = coordinates[0]; |
+ final float clickY = coordinates[1]; |
+ Log.v("ChromeTest", String.format("clickX %f clickY %f", clickX, clickY)); |
+ |
+ ChromeTabUtils.closeTabWithAction(getInstrumentation(), getActivity(), new Runnable() { |
+ @Override |
+ public void run() { |
+ if (isLandscape) { |
+ getInstrumentation().runOnMainSync(new SimulateTabSwipeOnMainThread( |
+ layoutManager, clickX, clickY, 0, swipeDirection * mTabsViewWidthDp)); |
+ } else { |
+ getInstrumentation().runOnMainSync(new SimulateTabSwipeOnMainThread( |
+ layoutManager, clickX, clickY, swipeDirection * mTabsViewHeightDp, 0)); |
+ } |
+ } |
+ }); |
+ assertTrue("Did not finish animation", |
+ CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ Layout layout = getActivity().getLayoutManager().getActiveLayout(); |
+ return !layout.isLayoutAnimating(); |
+ } |
+ })); |
+ } |
+ |
+ private void swipeToCloseNTabs(int number, boolean isLandscape, boolean isIncognito, |
+ int swipeDirection) |
+ throws InterruptedException { |
+ |
+ for (int i = number - 1; i >= 0; i--) { |
+ swipeToCloseTab(i, isLandscape, isIncognito, swipeDirection); |
+ } |
+ } |
+ |
+ /** |
+ * Test closing few tabs by swiping them in Overview portrait mode. |
+ */ |
+ @MediumTest |
+ @Restriction({RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE}) |
+ @Feature({"Android-TabSwitcher", "Main"}) |
+ public void testCloseTabPortrait() throws InterruptedException { |
+ startMainActivityWithURL(TestHttpServerClient.getUrl("chrome/test/data/android/test.html")); |
+ |
+ getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); |
+ |
+ int tabCount = getActivity().getCurrentTabModel().getCount(); |
+ ChromeTabUtils.newTabsFromMenu(getInstrumentation(), getActivity(), 3); |
+ assertEquals("wrong count after new tabs", tabCount + 3, |
+ getActivity().getCurrentTabModel().getCount()); |
+ |
+ showOverviewAndWaitForAnimation(); |
+ swipeToCloseNTabs(3, false, false, SWIPE_TO_LEFT_DIRECTION); |
+ |
+ assertEquals("Wrong tab counts after closing a few of them", |
+ tabCount, getActivity().getCurrentTabModel().getCount()); |
+ } |
+ |
+ /** |
+ * Test closing few tabs by swiping them in Overview landscape mode. |
+ */ |
+ @MediumTest |
+ @Feature({"Android-TabSwitcher", "Main"}) |
+ @Restriction({RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE}) |
+ public void testCloseTabLandscape() throws InterruptedException { |
+ startMainActivityWithURL(TestHttpServerClient.getUrl("chrome/test/data/android/test.html")); |
+ |
+ getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); |
+ |
+ int tabCount = getActivity().getCurrentTabModel().getCount(); |
+ ChromeTabUtils.newTabsFromMenu(getInstrumentation(), getActivity(), 3); |
+ assertEquals("wrong count after new tabs", tabCount + 3, |
+ getActivity().getCurrentTabModel().getCount()); |
+ |
+ showOverviewAndWaitForAnimation(); |
+ swipeToCloseTab(0, true, false, SWIPE_TO_LEFT_DIRECTION); |
+ swipeToCloseTab(0, true, false, SWIPE_TO_LEFT_DIRECTION); |
+ swipeToCloseTab(0, true, false, SWIPE_TO_LEFT_DIRECTION); |
+ |
+ assertEquals("Wrong tab counts after closing a few of them", |
+ tabCount, getActivity().getCurrentTabModel().getCount()); |
+ } |
+ |
+ /** |
+ * Test close Incognito tab by swiping in Overview Portrait mode. |
+ */ |
+ @MediumTest |
+ @Feature({"Android-TabSwitcher"}) |
+ @Restriction({RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE}) |
+ public void testCloseIncognitoTabPortrait() throws InterruptedException { |
+ getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); |
+ newIncognitoTabsFromMenu(2); |
+ |
+ showOverviewAndWaitForAnimation(); |
+ UiUtils.settleDownUI(getInstrumentation()); |
+ swipeToCloseNTabs(2, false, true, SWIPE_TO_LEFT_DIRECTION); |
+ } |
+ |
+ /** |
+ * Test close 5 Incognito tabs by swiping in Overview Portrait mode. |
+ */ |
+ @Feature({"Android-TabSwitcher"}) |
+ @MediumTest |
+ @Restriction({RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE}) |
+ public void testCloseFiveIncognitoTabPortrait() throws InterruptedException { |
+ getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); |
+ newIncognitoTabsFromMenu(5); |
+ |
+ showOverviewAndWaitForAnimation(); |
+ UiUtils.settleDownUI(getInstrumentation()); |
+ swipeToCloseNTabs(5, false, true, SWIPE_TO_LEFT_DIRECTION); |
+ } |
+ |
+ /** |
+ * Simple swipe gesture should not close tabs when two Tabstacks are open in Overview mode. |
+ * Test in Portrait Mode. |
+ */ |
+ @Restriction({RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE}) |
+ @MediumTest |
+ @Feature({"Android-TabSwitcher"}) |
+ public void testSwitchTabStackWithoutClosingTabsInPortrait() throws InterruptedException { |
+ getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); |
+ newIncognitoTabFromMenu(); |
+ ChromeTabUtils.newTabFromMenu(getInstrumentation(), getActivity()); |
+ |
+ showOverviewAndWaitForAnimation(); |
+ UiUtils.settleDownUI(getInstrumentation()); |
+ final int normalTabCount = getLayoutTabInStackCount(false); |
+ final int incognitoTabCount = getLayoutTabInStackCount(true); |
+ |
+ LayoutManagerChrome layoutManager = updateTabsViewSize(); |
+ |
+ // Swipe to Incognito Tabs. |
+ getInstrumentation().runOnMainSync(new SimulateTabSwipeOnMainThread(layoutManager, |
+ mTabsViewWidthDp - 20 , mTabsViewHeightDp / 2 , |
+ SWIPE_TO_LEFT_DIRECTION * mTabsViewWidthDp, 0)); |
+ UiUtils.settleDownUI(getInstrumentation()); |
+ assertTrue("Tabs Stack should have been changed to incognito.", |
+ getActivity().getCurrentTabModel().isIncognito()); |
+ assertEquals("Normal tabs count should be unchanged while switching to incognito tabs.", |
+ normalTabCount, getLayoutTabInStackCount(false)); |
+ |
+ // Swipe to regular Tabs. |
+ getInstrumentation().runOnMainSync(new SimulateTabSwipeOnMainThread(layoutManager, |
+ 20, mTabsViewHeightDp / 2, |
+ SWIPE_TO_RIGHT_DIRECTION * mTabsViewWidthDp, 0)); |
+ UiUtils.settleDownUI(getInstrumentation()); |
+ assertEquals("Incognito tabs count should be unchanged while switching back to normal " |
+ + "tab stack.", incognitoTabCount, getLayoutTabInStackCount(true)); |
+ assertFalse("Tabs Stack should have been changed to regular tabs.", |
+ getActivity().getCurrentTabModel().isIncognito()); |
+ assertEquals("Normal tabs count should be unchanged while switching back to normal tabs.", |
+ normalTabCount, getLayoutTabInStackCount(false)); |
+ } |
+ |
+ /** |
+ * Simple swipe gesture should not close tabs when two Tabstacks are open in Overview mode. |
+ * Test in Landscape Mode. |
+ */ |
+ /* |
+ @MediumTest |
+ @Feature({"Android-TabSwitcher"}) |
+ Bug http://crbug.com/157259 |
+ */ |
+ @Restriction({RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE}) |
+ @DisabledTest |
+ public void testSwitchTabStackWithoutClosingTabsInLandscape() throws InterruptedException { |
+ getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); |
+ newIncognitoTabFromMenu(); |
+ ChromeTabUtils.newTabFromMenu(getInstrumentation(), getActivity()); |
+ |
+ showOverviewAndWaitForAnimation(); |
+ UiUtils.settleDownUI(getInstrumentation()); |
+ final int normalTabCount = getLayoutTabInStackCount(false); |
+ final int incognitoTabCount = getLayoutTabInStackCount(true); |
+ |
+ LayoutManagerChrome layoutManager = updateTabsViewSize(); |
+ |
+ // Swipe to Incognito Tabs. |
+ getInstrumentation().runOnMainSync(new SimulateTabSwipeOnMainThread(layoutManager, |
+ mTabsViewWidthDp / 2 , mTabsViewHeightDp - 20 , |
+ 0, SWIPE_TO_LEFT_DIRECTION * mTabsViewWidthDp)); |
+ UiUtils.settleDownUI(getInstrumentation()); |
+ assertTrue("Tabs Stack should have been changed to incognito.", |
+ getActivity().getCurrentTabModel().isIncognito()); |
+ assertEquals("Normal tabs count should be unchanged while switching to incognito tabs.", |
+ normalTabCount, getLayoutTabInStackCount(false)); |
+ |
+ // Swipe to regular Tabs. |
+ getInstrumentation().runOnMainSync(new SimulateTabSwipeOnMainThread(layoutManager, |
+ mTabsViewWidthDp / 2, 20, |
+ 0, SWIPE_TO_RIGHT_DIRECTION * mTabsViewWidthDp)); |
+ UiUtils.settleDownUI(getInstrumentation()); |
+ assertEquals("Incognito tabs count should be unchanged while switching back to normal " |
+ + "tab stack.", incognitoTabCount, getLayoutTabInStackCount(true)); |
+ assertFalse("Tabs Stack should have been changed to regular tabs.", |
+ getActivity().getCurrentTabModel().isIncognito()); |
+ assertEquals("Normal tabs count should be unchanged while switching back to normal tabs.", |
+ normalTabCount, getLayoutTabInStackCount(false)); |
+ } |
+ |
+ /** |
+ * Test close Incognito tab by swiping in Overview Landscape mode. |
+ */ |
+ @MediumTest |
+ @Feature({"Android-TabSwitcher"}) |
+ @Restriction({RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE}) |
+ public void testCloseIncognitoTabLandscape() throws InterruptedException { |
+ getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); |
+ newIncognitoTabFromMenu(); |
+ |
+ showOverviewAndWaitForAnimation(); |
+ UiUtils.settleDownUI(getInstrumentation()); |
+ swipeToCloseTab(0, true, true, SWIPE_TO_LEFT_DIRECTION); |
+ } |
+ |
+ /** |
+ * Test close 5 Incognito tabs by swiping in Overview Landscape mode. |
+ */ |
+ @MediumTest |
+ @Feature({"Android-TabSwitcher"}) |
+ @Restriction({RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE}) |
+ public void testCloseFiveIncognitoTabLandscape() throws InterruptedException { |
+ getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); |
+ newIncognitoTabsFromMenu(5); |
+ |
+ showOverviewAndWaitForAnimation(); |
+ UiUtils.settleDownUI(getInstrumentation()); |
+ swipeToCloseNTabs(5, true, true, SWIPE_TO_LEFT_DIRECTION); |
+ } |
+ |
+ /** |
+ * Test that we can safely close a tab during a fling (http://b/issue?id=5364043) |
+ */ |
+ @SmallTest |
+ @Feature({"Android-TabSwitcher"}) |
+ public void testCloseTabDuringFling() throws InterruptedException { |
+ loadUrlInNewTab(TestHttpServerClient.getUrl( |
+ "chrome/test/data/android/tabstest/text_page.html")); |
+ getInstrumentation().runOnMainSync(new Runnable() { |
+ @Override |
+ public void run() { |
+ ContentViewCore view = getActivity().getActivityTab().getContentViewCore(); |
+ view.flingForTest(SystemClock.uptimeMillis(), 0, 0, 0, -2000); |
+ } |
+ }); |
+ ChromeTabUtils.closeCurrentTab(getInstrumentation(), getActivity()); |
+ } |
+ |
+ /** |
+ * Flaky on instrumentation-yakju-clankium-ics. See https://crbug.com/431296. |
+ * @Restriction(RESTRICTION_TYPE_PHONE) |
+ * @MediumTest |
+ * @Feature({"Android-TabSwitcher"}) |
+ */ |
+ @FlakyTest |
+ public void testQuickSwitchBetweenTabAndSwitcherMode() throws InterruptedException { |
+ final String[] urls = { |
+ TestHttpServerClient.getUrl("chrome/test/data/android/navigate/one.html"), |
+ TestHttpServerClient.getUrl("chrome/test/data/android/navigate/two.html"), |
+ TestHttpServerClient.getUrl("chrome/test/data/android/navigate/three.html")}; |
+ |
+ for (String url : urls) { |
+ loadUrlInNewTab(url); |
+ } |
+ |
+ int lastUrlIndex = urls.length - 1; |
+ |
+ View button = getActivity().findViewById(R.id.tab_switcher_button); |
+ assertNotNull("Could not find 'tab_switcher_button'", button); |
+ |
+ for (int i = 0; i < 15; i++) { |
+ singleClickView(button, button.getWidth() / 2, button.getHeight() / 2); |
+ // Switch back to the tab view from the tab-switcher mode. |
+ singleClickView(button, button.getWidth() / 2, button.getHeight() / 2); |
+ |
+ assertEquals("URL mismatch after switching back to the tab from tab-switch mode", |
+ urls[lastUrlIndex], getActivity().getActivityTab().getUrl()); |
+ } |
+ } |
+ |
+ /** |
+ * Open an incognito tab from menu and verify its property. |
+ */ |
+ @MediumTest |
+ @Feature({"Android-TabSwitcher"}) |
+ public void testOpenIncognitoTab() throws InterruptedException { |
+ newIncognitoTabFromMenu(); |
+ |
+ assertTrue("Current Tab should be an incognito tab.", |
+ getActivity().getActivityTab().isIncognito()); |
+ } |
+ |
+ /** |
+ * Test NewTab button on the browser toolbar. |
+ * Restricted to phones due crbug.com/429671. |
+ */ |
+ @MediumTest |
+ @Feature({"Android-TabSwitcher"}) |
+ @Restriction(RESTRICTION_TYPE_PHONE) |
+ public void testNewTabButton() throws InterruptedException { |
+ MenuUtils.invokeCustomMenuActionSync(getInstrumentation(), getActivity(), |
+ R.id.close_all_tabs_menu_id); |
+ UiUtils.settleDownUI(getInstrumentation()); |
+ |
+ assertTrue("Should be in overview mode", CriteriaHelper.pollForCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ return getActivity().isInOverviewMode(); |
+ } |
+ })); |
+ |
+ int initialTabCount = getActivity().getCurrentTabModel().getCount(); |
+ assertEquals("Tab count is expected to be 0 after closing all the tabs", |
+ 0, initialTabCount); |
+ |
+ ChromeTabUtils.clickNewTabButton(this, this); |
+ |
+ int newTabCount = getActivity().getCurrentTabModel().getCount(); |
+ assertEquals("Tab count is expected to be 1 after clicking Newtab button", |
+ 1, newTabCount); |
+ } |
+ |
+ @MediumTest |
+ @Feature({"Android-TabSwitcher"}) |
+ @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) |
+ public void testToolbarSwipeOnlyTab() throws InterruptedException { |
+ final TabModel tabModel = getActivity().getTabModelSelector().getModel(false); |
+ |
+ assertEquals("Incorrect starting index", 0, tabModel.index()); |
+ runToolbarSideSwipeTestOnCurrentModel(ScrollDirection.RIGHT, 0, false); |
+ runToolbarSideSwipeTestOnCurrentModel(ScrollDirection.LEFT, 0, false); |
+ } |
+ |
+ @MediumTest |
+ @Feature({"Android-TabSwitcher"}) |
+ @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) |
+ public void testToolbarSwipePrevTab() throws InterruptedException { |
+ ChromeTabUtils.newTabFromMenu(getInstrumentation(), getActivity()); |
+ UiUtils.settleDownUI(getInstrumentation()); |
+ |
+ final TabModel tabModel = getActivity().getTabModelSelector().getModel(false); |
+ |
+ assertEquals("Incorrect starting index", 1, tabModel.index()); |
+ runToolbarSideSwipeTestOnCurrentModel(ScrollDirection.RIGHT, 0, true); |
+ } |
+ |
+ @MediumTest |
+ @Feature({"Android-TabSwitcher"}) |
+ @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) |
+ public void testToolbarSwipeNextTab() throws InterruptedException { |
+ ChromeTabUtils.newTabFromMenu(getInstrumentation(), getActivity()); |
+ ChromeTabUtils.switchTabInCurrentTabModel(getActivity(), 0); |
+ UiUtils.settleDownUI(getInstrumentation()); |
+ |
+ final TabModel tabModel = getActivity().getTabModelSelector().getModel(false); |
+ |
+ assertEquals("Incorrect starting index", 0, tabModel.index()); |
+ runToolbarSideSwipeTestOnCurrentModel(ScrollDirection.LEFT, 1, true); |
+ } |
+ |
+ @MediumTest |
+ @Feature({"Android-TabSwitcher"}) |
+ @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) |
+ public void testToolbarSwipePrevTabNone() throws InterruptedException { |
+ ChromeTabUtils.newTabFromMenu(getInstrumentation(), getActivity()); |
+ ChromeTabUtils.switchTabInCurrentTabModel(getActivity(), 0); |
+ UiUtils.settleDownUI(getInstrumentation()); |
+ |
+ final TabModel tabModel = getActivity().getTabModelSelector().getModel(false); |
+ |
+ assertEquals("Incorrect starting index", 0, tabModel.index()); |
+ runToolbarSideSwipeTestOnCurrentModel(ScrollDirection.RIGHT, 0, false); |
+ } |
+ |
+ @MediumTest |
+ @Feature({"Android-TabSwitcher"}) |
+ @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) |
+ public void testToolbarSwipeNextTabNone() throws InterruptedException { |
+ ChromeTabUtils.newTabFromMenu(getInstrumentation(), getActivity()); |
+ UiUtils.settleDownUI(getInstrumentation()); |
+ |
+ final TabModel tabModel = getActivity().getTabModelSelector().getModel(false); |
+ |
+ assertEquals("Incorrect starting index", 1, tabModel.index()); |
+ runToolbarSideSwipeTestOnCurrentModel(ScrollDirection.LEFT, 1, false); |
+ } |
+ |
+ /** |
+ * Bug: crbug.com/392656 |
+ * @MediumTest |
+ * @Feature({"Android-TabSwitcher"}) |
+ * @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) |
+ */ |
+ @DisabledTest |
+ public void testToolbarSwipeNextThenPrevTab() throws InterruptedException { |
+ ChromeTabUtils.newTabFromMenu(getInstrumentation(), getActivity()); |
+ ChromeTabUtils.switchTabInCurrentTabModel(getActivity(), 0); |
+ UiUtils.settleDownUI(getInstrumentation()); |
+ |
+ final TabModel tabModel = getActivity().getTabModelSelector().getModel(false); |
+ |
+ assertEquals("Incorrect starting index", 0, tabModel.index()); |
+ runToolbarSideSwipeTestOnCurrentModel(ScrollDirection.LEFT, 1, true); |
+ |
+ assertEquals("Incorrect starting index", 1, tabModel.index()); |
+ runToolbarSideSwipeTestOnCurrentModel(ScrollDirection.RIGHT, 0, true); |
+ } |
+ |
+ /** |
+ * Bug: crbug.com/392656 |
+ * @MediumTest |
+ * @Feature({"Android-TabSwitcher"}) |
+ * @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) |
+ */ |
+ @DisabledTest |
+ public void testToolbarSwipeNextThenPrevTabIncognito() throws InterruptedException { |
+ newIncognitoTabFromMenu(); |
+ newIncognitoTabFromMenu(); |
+ ChromeTabUtils.switchTabInCurrentTabModel(getActivity(), 0); |
+ UiUtils.settleDownUI(getInstrumentation()); |
+ |
+ final TabModel tabModel = getActivity().getTabModelSelector().getModel(true); |
+ |
+ assertEquals("Incorrect starting index", 0, tabModel.index()); |
+ runToolbarSideSwipeTestOnCurrentModel(ScrollDirection.LEFT, 1, true); |
+ |
+ assertEquals("Incorrect starting index", 1, tabModel.index()); |
+ runToolbarSideSwipeTestOnCurrentModel(ScrollDirection.RIGHT, 0, true); |
+ } |
+ |
+ private void runToolbarSideSwipeTestOnCurrentModel(ScrollDirection direction, int finalIndex, |
+ boolean expectsSelection) throws InterruptedException { |
+ final CallbackHelper selectCallback = new CallbackHelper(); |
+ final int id = getActivity().getCurrentTabModel().getTabAt(finalIndex).getId(); |
+ final TabModelObserver observer = new EmptyTabModelObserver() { |
+ @Override |
+ public void didSelectTab(Tab tab, TabSelectionType type, int lastId) { |
+ if (tab.getId() == id) selectCallback.notifyCalled(); |
+ } |
+ }; |
+ |
+ if (expectsSelection) { |
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
+ @Override |
+ public void run() { |
+ TabModelSelector selector = getActivity().getTabModelSelector(); |
+ for (TabModel tabModel : selector.getModels()) { |
+ tabModel.addObserver(observer); |
+ } |
+ } |
+ }); |
+ } |
+ |
+ performToolbarSideSwipe(direction); |
+ waitForStaticLayout(); |
+ assertEquals("Index after toolbar side swipe is incorrect", finalIndex, |
+ getActivity().getCurrentTabModel().index()); |
+ |
+ if (expectsSelection) { |
+ try { |
+ selectCallback.waitForCallback(0); |
+ } catch (TimeoutException e) { |
+ Assert.fail("Tab selected event was never received"); |
+ } |
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
+ @Override |
+ public void run() { |
+ TabModelSelector selector = getActivity().getTabModelSelector(); |
+ for (TabModel tabModel : selector.getModels()) { |
+ tabModel.removeObserver(observer); |
+ } |
+ } |
+ }); |
+ } |
+ } |
+ |
+ |
+ private void performToolbarSideSwipe(ScrollDirection direction) { |
+ assertTrue("Unexpected direction for side swipe " + direction, |
+ direction == ScrollDirection.LEFT || direction == ScrollDirection.RIGHT); |
+ final View toolbar = getActivity().findViewById(R.id.toolbar); |
+ int[] toolbarPos = new int[2]; |
+ toolbar.getLocationOnScreen(toolbarPos); |
+ final int width = toolbar.getWidth(); |
+ final int height = toolbar.getHeight(); |
+ |
+ final int fromX = toolbarPos[0] + width / 2; |
+ final int toX = toolbarPos[0] + (direction == ScrollDirection.LEFT ? 0 : width); |
+ final int y = toolbarPos[1] + height / 2; |
+ final int stepCount = 10; |
+ |
+ long downTime = SystemClock.uptimeMillis(); |
+ dragStart(fromX, y, downTime); |
+ dragTo(fromX, toX, y, y, stepCount, downTime); |
+ dragEnd(toX, y, downTime); |
+ } |
+ |
+ private void waitForStaticLayout() throws InterruptedException { |
+ final Callable<Boolean> callable = new Callable<Boolean>() { |
+ @Override |
+ public Boolean call() throws Exception { |
+ CompositorViewHolder compositorViewHolder = (CompositorViewHolder) |
+ getActivity().findViewById(R.id.compositor_view_holder); |
+ LayoutManager layoutManager = compositorViewHolder.getLayoutManager(); |
+ |
+ return layoutManager.getActiveLayout() instanceof StaticLayout; |
+ } |
+ }; |
+ |
+ assertTrue("Static Layout never selected after side swipe", |
+ CriteriaHelper.pollForCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ return ThreadUtils.runOnUiThreadBlockingNoException(callable); |
+ } |
+ })); |
+ } |
+ |
+ /** |
+ * Test that swipes and tab transitions are not causing URL bar to be focused. |
+ */ |
+ @MediumTest |
+ @Restriction({RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE}) |
+ @Feature({"Android-TabSwitcher"}) |
+ public void testOSKIsNotShownDuringSwipe() throws InterruptedException { |
+ final View urlBar = getActivity().findViewById(R.id.url_bar); |
+ final LayoutManagerChrome layoutManager = updateTabsViewSize(); |
+ final EdgeSwipeHandler edgeSwipeHandler = layoutManager.getTopSwipeHandler(); |
+ |
+ UiUtils.settleDownUI(getInstrumentation()); |
+ getInstrumentation().runOnMainSync( |
+ new Runnable() { |
+ @Override |
+ public void run() { |
+ urlBar.requestFocus(); |
+ } |
+ }); |
+ UiUtils.settleDownUI(getInstrumentation()); |
+ |
+ getInstrumentation().runOnMainSync( |
+ new Runnable() { |
+ @Override |
+ public void run() { |
+ urlBar.clearFocus(); |
+ } |
+ }); |
+ UiUtils.settleDownUI(getInstrumentation()); |
+ ChromeTabUtils.newTabFromMenu(getInstrumentation(), getActivity()); |
+ UiUtils.settleDownUI(getInstrumentation()); |
+ |
+ assertFalse("Keyboard somehow got shown", |
+ org.chromium.ui.UiUtils.isKeyboardShowing(getActivity(), urlBar)); |
+ |
+ ThreadUtils.runOnUiThread(new Runnable() { |
+ @Override |
+ public void run() { |
+ edgeSwipeHandler.swipeStarted(ScrollDirection.RIGHT, 0, 0); |
+ float swipeXChange = mTabsViewWidthDp / 2.f; |
+ edgeSwipeHandler.swipeUpdated( |
+ swipeXChange, 0.f, swipeXChange, 0.f, swipeXChange, 0.f); |
+ } |
+ }); |
+ |
+ assertTrue("Layout still requesting Tab Android view be attached", |
+ CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ LayoutManager driver = getActivity().getLayoutManager(); |
+ return !driver.getActiveLayout().shouldDisplayContentOverlay(); |
+ } |
+ })); |
+ |
+ ThreadUtils.runOnUiThread(new Runnable() { |
+ @Override |
+ public void run() { |
+ assertFalse("Keyboard should be hidden while swiping", |
+ org.chromium.ui.UiUtils.isKeyboardShowing(getActivity(), urlBar)); |
+ edgeSwipeHandler.swipeFinished(); |
+ } |
+ }); |
+ |
+ assertTrue("Layout not requesting Tab Android view be attached", |
+ CriteriaHelper.pollForUIThreadCriteria(new Criteria() { |
+ @Override |
+ public boolean isSatisfied() { |
+ LayoutManager driver = getActivity().getLayoutManager(); |
+ return driver.getActiveLayout().shouldDisplayContentOverlay(); |
+ } |
+ })); |
+ |
+ assertFalse("Keyboard should not be shown", |
+ org.chromium.ui.UiUtils.isKeyboardShowing(getActivity(), urlBar)); |
+ } |
+ |
+ /** |
+ * Test that orientation changes cause the live tab reflow. |
+ */ |
+ @MediumTest |
+ @Feature({"Android-TabSwitcher"}) |
+ @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE) |
+ public void testOrientationChangeCausesLiveTabReflowInNormalView() |
+ throws InterruptedException, TimeoutException { |
+ getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); |
+ ChromeTabUtils.newTabFromMenu(getInstrumentation(), getActivity()); |
+ loadUrl(RESIZE_TEST_URL); |
+ final WebContents webContents = |
+ getActivity().getActivityTab().getWebContents(); |
+ |
+ JavaScriptUtils.executeJavaScriptAndWaitForResult(webContents, |
+ "resizeHappened = false;"); |
+ getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); |
+ UiUtils.settleDownUI(getInstrumentation()); |
+ assertEquals("onresize event wasn't received by the tab (normal view)", |
+ "true", |
+ JavaScriptUtils.executeJavaScriptAndWaitForResult( |
+ webContents, "resizeHappened", |
+ WAIT_RESIZE_TIMEOUT_MS, TimeUnit.MILLISECONDS)); |
+ } |
+ |
+ /** |
+ * Test that orientation changes cause the live tab reflow. |
+ */ |
+ @MediumTest |
+ @Feature({"Android-TabSwitcher"}) |
+ @Restriction({RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE}) |
+ public void testOrientationChangeCausesLiveTabReflowInTabSwitcher() |
+ throws InterruptedException, TimeoutException { |
+ getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); |
+ ChromeTabUtils.newTabFromMenu(getInstrumentation(), getActivity()); |
+ loadUrl(RESIZE_TEST_URL); |
+ final WebContents webContents = |
+ getActivity().getActivityTab().getWebContents(); |
+ |
+ showOverviewAndWaitForAnimation(); |
+ JavaScriptUtils.executeJavaScriptAndWaitForResult(webContents, |
+ "resizeHappened = false;"); |
+ getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); |
+ UiUtils.settleDownUI(getInstrumentation()); |
+ assertEquals("onresize event wasn't received by the live tab (tabswitcher, to Landscape)", |
+ "true", |
+ JavaScriptUtils.executeJavaScriptAndWaitForResult( |
+ webContents, "resizeHappened", |
+ WAIT_RESIZE_TIMEOUT_MS, TimeUnit.MILLISECONDS)); |
+ |
+ JavaScriptUtils.executeJavaScriptAndWaitForResult(webContents, |
+ "resizeHappened = false;"); |
+ getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); |
+ UiUtils.settleDownUI(getInstrumentation()); |
+ assertEquals("onresize event wasn't received by the live tab (tabswitcher, to Portrait)", |
+ "true", |
+ JavaScriptUtils.executeJavaScriptAndWaitForResult(webContents, |
+ "resizeHappened", WAIT_RESIZE_TIMEOUT_MS, TimeUnit.MILLISECONDS)); |
+ } |
+ |
+ @MediumTest |
+ @Feature({"Android-TabSwitcher"}) |
+ public void testLastClosedUndoableTabGetsHidden() { |
+ final TabModel model = getActivity().getTabModelSelector().getCurrentModel(); |
+ final Tab tab = TabModelUtils.getCurrentTab(model); |
+ |
+ assertEquals("Too many tabs at startup", 1, model.getCount()); |
+ |
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
+ @Override |
+ public void run() { |
+ model.closeTab(tab, false, false, true); |
+ } |
+ }); |
+ |
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
+ @Override |
+ public void run() { |
+ assertTrue("Tab close is not undoable", model.isClosurePending(tab.getId())); |
+ assertTrue("Tab was not hidden", tab.isHidden()); |
+ } |
+ }); |
+ } |
+ |
+ @MediumTest |
+ @Feature({"Android-TabSwitcher"}) |
+ public void testLastClosedTabTriggersNotifyChangedCall() { |
+ final TabModel model = getActivity().getTabModelSelector().getCurrentModel(); |
+ final Tab tab = TabModelUtils.getCurrentTab(model); |
+ final TabModelSelector selector = getActivity().getTabModelSelector(); |
+ mNotifyChangedCalled = false; |
+ |
+ selector.addObserver(new EmptyTabModelSelectorObserver() { |
+ @Override |
+ public void onChange() { |
+ mNotifyChangedCalled = true; |
+ } |
+ }); |
+ |
+ assertEquals("Too many tabs at startup", 1, model.getCount()); |
+ |
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
+ @Override |
+ public void run() { |
+ model.closeTab(tab, false, false, true); |
+ } |
+ }); |
+ |
+ assertTrue("notifyChanged() was not called", mNotifyChangedCalled); |
+ } |
+ |
+ @Smoke |
+ @Feature({"Android-TabSwitcher"}) |
+ public void testTabsAreDestroyedOnModelDestruction() throws InterruptedException { |
+ startMainActivityOnBlankPage(); |
+ final TabModelSelectorImpl selector = |
+ (TabModelSelectorImpl) getActivity().getTabModelSelector(); |
+ final Tab tab = getActivity().getActivityTab(); |
+ |
+ final AtomicBoolean webContentsDestroyCalled = new AtomicBoolean(); |
+ |
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() { |
+ @Override |
+ @SuppressFBWarnings("DLS_DEAD_LOCAL_STORE") |
+ public void run() { |
+ @SuppressWarnings("unused") // Avoid GC of observer |
+ WebContentsObserver observer = new WebContentsObserver(tab.getWebContents()) { |
+ @Override |
+ public void destroy() { |
+ super.destroy(); |
+ webContentsDestroyCalled.set(true); |
+ } |
+ }; |
+ |
+ assertNotNull("No initial tab at startup", tab); |
+ assertNotNull("Tab does not have a web contents", tab.getWebContents()); |
+ assertTrue("Tab is destroyed", tab.isInitialized()); |
+ |
+ selector.destroy(); |
+ |
+ assertNull("Tab still has a web contents", tab.getWebContents()); |
+ assertFalse("Tab was not destroyed", tab.isInitialized()); |
+ } |
+ }); |
+ |
+ assertTrue("WebContentsObserver was never destroyed", webContentsDestroyCalled.get()); |
+ } |
+ |
+ @Override |
+ public void startMainActivity() throws InterruptedException { |
+ float dpToPx = getInstrumentation().getContext().getResources().getDisplayMetrics().density; |
+ mPxToDp = 1.0f / dpToPx; |
+ |
+ // Exclude the tests that can launch directly to a page other than the NTP. |
+ if (getName().equals("testOpenAndCloseNewTabButton") |
+ || getName().equals("testSwitchToTabThatDoesNotHaveThumbnail") |
+ || getName().equals("testCloseTabPortrait") |
+ || getName().equals("testCloseTabLandscape") |
+ || getName().equals("testTabsAreDestroyedOnModelDestruction")) { |
+ return; |
+ } |
+ |
+ if (getName().equals("testSpawnPopupOnBackgroundTab")) { |
+ CommandLine.getInstance().appendSwitch(ContentSwitches.DISABLE_POPUP_BLOCKING); |
+ } |
+ startMainActivityFromLauncher(); |
+ } |
+} |