blob: 9448ee1c275c708a3ba59c671498567997827d96 [file] [log] [blame]
// 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 android.content.DialogInterface;
import android.content.pm.ActivityInfo;
import android.os.Debug;
import android.os.SystemClock;
import android.support.test.filters.LargeTest;
import android.support.test.filters.MediumTest;
import android.support.test.filters.SmallTest;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import org.chromium.base.ThreadUtils;
import org.chromium.base.annotations.SuppressFBWarnings;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.DisabledTest;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.FlakyTest;
import org.chromium.base.test.util.Restriction;
import org.chromium.base.test.util.RetryOnFailure;
import org.chromium.base.test.util.UrlUtils;
import org.chromium.chrome.R;
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.tab.Tab;
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.tabmodel.TabbedModeTabPersistencePolicy;
import org.chromium.chrome.browser.toolbar.ToolbarPhone;
import org.chromium.chrome.test.ChromeTabbedActivityTestBase;
import org.chromium.chrome.test.util.ApplicationTestUtils;
import org.chromium.chrome.test.util.ChromeRestriction;
import org.chromium.chrome.test.util.ChromeTabUtils;
import org.chromium.chrome.test.util.MenuUtils;
import org.chromium.chrome.test.util.NewTabPageTestUtils;
import org.chromium.chrome.test.util.OverviewModeBehaviorWatcher;
import org.chromium.content.browser.ContentViewCore;
import org.chromium.content.browser.test.util.Criteria;
import org.chromium.content.browser.test.util.CriteriaHelper;
import org.chromium.content.browser.test.util.DOMUtils;
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 org.chromium.net.test.EmbeddedTestServer;
import java.io.File;
import java.util.Locale;
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 EmbeddedTestServer mTestServer;
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 INITIAL_SIZE_TEST_URL = UrlUtils.encodeHtmlDataUri(
"<html><head><meta name=\"viewport\" content=\"width=device-width\">"
+ "<script>"
+ " document.writeln(window.innerWidth + ',' + window.innerHeight);"
+ "</script></head>"
+ "<body>"
+ "</body></html>");
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>");
@Override
protected void tearDown() throws Exception {
if (mTestServer != null) {
mTestServer.stopAndDestroyServer();
}
super.tearDown();
}
/**
* 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)
@CommandLineFlags.Add(ContentSwitches.DISABLE_POPUP_BLOCKING)
@RetryOnFailure
public void testSpawnPopupOnBackgroundTab() throws InterruptedException, TimeoutException {
mTestServer = EmbeddedTestServer.createAndStartServer(getInstrumentation().getContext());
loadUrl(mTestServer.getURL(TEST_FILE_PATH));
final Tab tab = getActivity().getActivityTab();
newIncognitoTabFromMenu();
ThreadUtils.runOnUiThreadBlocking(new Runnable() {
@Override
public void run() {
tab.getWebContents().evaluateJavaScriptForTests(
"(function() {"
+ " window.open('www.google.com');"
+ "})()",
null);
}
});
CriteriaHelper.pollUiThread(Criteria.equals(2, new Callable<Integer>() {
@Override
public Integer call() {
return getActivity().getTabModelSelector().getModel(false).getCount();
}
}));
}
@MediumTest
@RetryOnFailure
public void testAlertDialogDoesNotChangeActiveModel() throws InterruptedException {
mTestServer = EmbeddedTestServer.createAndStartServer(getInstrumentation().getContext());
newIncognitoTabFromMenu();
loadUrl(mTestServer.getURL(TEST_FILE_PATH));
final Tab tab = getActivity().getActivityTab();
ThreadUtils.runOnUiThreadBlocking(new Runnable() {
@Override
public void run() {
tab.getWebContents().evaluateJavaScriptForTests(
"(function() {"
+ " alert('hi');"
+ "})()",
null);
}
});
final AtomicReference<JavascriptAppModalDialog> dialog =
new AtomicReference<>();
CriteriaHelper.pollInstrumentationThread(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.pollInstrumentationThread(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 {
mTestServer = EmbeddedTestServer.createAndStartServer(getInstrumentation().getContext());
startMainActivityWithURL(mTestServer.getURL(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);
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);
overviewModeWatcher.waitForBehavior();
getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
assertEquals("The tab count is wrong",
tabCount + 1, getActivity().getCurrentTabModel().getCount());
}
});
CriteriaHelper.pollUiThread(new Criteria() {
@Override
public boolean isSatisfied() {
Tab tab = getActivity().getCurrentTabModel().getTabAt(1);
String title = tab.getTitle().toLowerCase(Locale.US);
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());
}
});
}
private void assertWaitForKeyboardStatus(final boolean show) throws InterruptedException {
CriteriaHelper.pollUiThread(new Criteria() {
@Override
public boolean isSatisfied() {
updateFailureReason("expected keyboard show: " + show);
return show
== org.chromium.ui.UiUtils.isKeyboardShowing(
getActivity(), getActivity().getTabsView());
}
});
}
/**
* Verify that opening a new tab, switching to an existing tab and closing current tab hide
* keyboard.
*/
@LargeTest
@Restriction(ChromeRestriction.RESTRICTION_TYPE_TABLET)
@Feature({"Android-TabSwitcher"})
@RetryOnFailure
public void testHideKeyboard() throws Exception {
mTestServer = EmbeddedTestServer.createAndStartServer(getInstrumentation().getContext());
// Open a new tab(The 1st tab) and click node.
ChromeTabUtils.fullyLoadUrlInNewTab(
getInstrumentation(), getActivity(), mTestServer.getURL(TEST_FILE_PATH), false);
assertEquals("Failed to click node.", true,
DOMUtils.clickNode(
this, getActivity().getActivityTab().getContentViewCore(), "input_text"));
assertWaitForKeyboardStatus(true);
// Open a new tab(the 2nd tab).
ChromeTabUtils.fullyLoadUrlInNewTab(
getInstrumentation(), getActivity(), mTestServer.getURL(TEST_FILE_PATH), false);
assertWaitForKeyboardStatus(false);
// Click node in the 2nd tab.
DOMUtils.clickNode(this, getActivity().getActivityTab().getContentViewCore(), "input_text");
assertWaitForKeyboardStatus(true);
// Switch to the 1st tab.
ChromeTabUtils.switchTabInCurrentTabModel(getActivity(), 1);
assertWaitForKeyboardStatus(false);
// Click node in the 1st tab.
DOMUtils.clickNode(this, getActivity().getActivityTab().getContentViewCore(), "input_text");
assertWaitForKeyboardStatus(true);
// Close current tab(the 1st tab).
ChromeTabUtils.closeCurrentTab(getInstrumentation(), getActivity());
assertWaitForKeyboardStatus(false);
}
/**
* Verify that opening a new window hides keyboard.
*/
@MediumTest
@Feature({"Android-TabSwitcher"})
@RetryOnFailure
public void testHideKeyboardWhenOpeningWindow() throws Exception {
mTestServer = EmbeddedTestServer.createAndStartServer(getInstrumentation().getContext());
// Open a new tab and click an editable node.
ChromeTabUtils.fullyLoadUrlInNewTab(
getInstrumentation(), getActivity(), mTestServer.getURL(TEST_FILE_PATH), false);
assertEquals("Failed to click textarea.", true,
DOMUtils.clickNode(
this, getActivity().getActivityTab().getContentViewCore(), "textarea"));
assertWaitForKeyboardStatus(true);
// Click the button to open a new window.
assertEquals("Failed to click button.", true,
DOMUtils.clickNode(
this, getActivity().getActivityTab().getContentViewCore(), "button"));
assertWaitForKeyboardStatus(false);
}
/**
* Verify that opening a new tab and navigating immediately sets a size on the newly created
* renderer. https://crbug.com/434477.
* @throws InterruptedException
* @throws TimeoutException
*/
@SmallTest
@RetryOnFailure
public void testNewTabSetsContentViewSize() throws InterruptedException, TimeoutException {
ChromeTabUtils.newTabFromMenu(getInstrumentation(), getActivity());
getInstrumentation().waitForIdleSync();
// Make sure we're on the NTP
Tab tab = getActivity().getActivityTab();
NewTabPageTestUtils.waitForNtpLoaded(tab);
loadUrl(INITIAL_SIZE_TEST_URL);
final WebContents webContents = tab.getWebContents();
String innerText = JavaScriptUtils.executeJavaScriptAndWaitForResult(
webContents, "document.body.innerText").replace("\"", "");
DisplayMetrics metrics = getActivity().getResources().getDisplayMetrics();
// For non-integer pixel ratios like the N7v1 (1.333...), the layout system will actually
// ceil the width.
int expectedWidth = (int) Math.ceil(metrics.widthPixels / metrics.density);
String[] nums = innerText.split(",");
assertTrue(nums.length == 2);
int innerWidth = Integer.parseInt(nums[0]);
int innerHeight = Integer.parseInt(nums[1]);
assertEquals(expectedWidth, innerWidth);
// Height can be affected by browser controls so just make sure it's non-0.
assertTrue("innerHeight was not set by page load time", innerHeight > 0);
}
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);
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({ChromeRestriction.RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE})
@Feature({"Android-TabSwitcher"})
@RetryOnFailure
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
* @Restriction({RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE})
* @LargeTest
* @Feature({"Android-TabSwitcher"})
*/
@FlakyTest(message = "crbug.com/170179")
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 {
mTestServer = EmbeddedTestServer.createAndStartServer(getInstrumentation().getContext());
final int burstSize = 5;
final String url = mTestServer.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"})
*/
@FlakyTest(message = "crbug.com/223110")
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 {
mTestServer = EmbeddedTestServer.createAndStartServer(getInstrumentation().getContext());
final String url = mTestServer.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"})
*/
@DisabledTest(message = "crbug.com/156746")
@Restriction({ChromeRestriction.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().getLayoutTabsToRender().length;
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.
*/
/**
* @LargeTest
* @Feature({"Android-TabSwitcher"})
*/
@FlakyTest(message = "crbug.com/170179")
@Restriction({ChromeRestriction.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.
*/
/**
* @LargeTest
* @Feature({"Android-TabSwitcher"})
*/
@FlakyTest(message = "crbug.com/303319")
@Restriction(ChromeRestriction.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(ChromeRestriction.RESTRICTION_TYPE_PHONE)
@Feature({"Android-TabSwitcher"})
@RetryOnFailure
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({ChromeRestriction.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(Locale.US,
"Tab %d's final draw Y, %f, should exceed the view height, %f.",
i, y, mTabsViewHeightDp),
y >= mTabsViewHeightDp);
}
}
/**
* @LargeTest
* @Feature({"Android-TabSwitcher"})
*/
@FlakyTest(message = "crbug.com/170179")
@Restriction({ChromeRestriction.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(Locale.US,
"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(ChromeRestriction.RESTRICTION_TYPE_PHONE)
@Feature({"Android-TabSwitcher"})
@RetryOnFailure
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));
}
}
});
CriteriaHelper.pollUiThread(new Criteria("Did not finish animation") {
@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({ChromeRestriction.RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE})
@Feature({"Android-TabSwitcher", "Main"})
@RetryOnFailure
public void testCloseTabPortrait() throws InterruptedException {
mTestServer = EmbeddedTestServer.createAndStartServer(getInstrumentation().getContext());
startMainActivityWithURL(mTestServer.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({ChromeRestriction.RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE})
@RetryOnFailure
public void testCloseTabLandscape() throws InterruptedException {
mTestServer = EmbeddedTestServer.createAndStartServer(getInstrumentation().getContext());
startMainActivityWithURL(mTestServer.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({ChromeRestriction.RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE})
@RetryOnFailure
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({ChromeRestriction.RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE})
@RetryOnFailure
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({ChromeRestriction.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"})
*/
@Restriction({ChromeRestriction.RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE})
@DisabledTest(message = "crbug.com/157259")
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({ChromeRestriction.RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE})
@RetryOnFailure
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({ChromeRestriction.RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE})
@RetryOnFailure
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"})
@RetryOnFailure
public void testCloseTabDuringFling() throws InterruptedException {
mTestServer = EmbeddedTestServer.createAndStartServer(getInstrumentation().getContext());
loadUrlInNewTab(mTestServer.getURL(
"/chrome/test/data/android/tabstest/text_page.html"));
getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
ContentViewCore view = getActivity().getActivityTab().getContentViewCore();
view.flingViewport(SystemClock.uptimeMillis(), 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 {
mTestServer = EmbeddedTestServer.createAndStartServer(getInstrumentation().getContext());
final String[] urls = {
mTestServer.getURL("/chrome/test/data/android/navigate/one.html"),
mTestServer.getURL("/chrome/test/data/android/navigate/two.html"),
mTestServer.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);
// Switch back to the tab view from the tab-switcher mode.
singleClickView(button);
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"})
@RetryOnFailure
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(ChromeRestriction.RESTRICTION_TYPE_PHONE)
@RetryOnFailure
public void testNewTabButton() throws InterruptedException {
MenuUtils.invokeCustomMenuActionSync(getInstrumentation(), getActivity(),
R.id.close_all_tabs_menu_id);
UiUtils.settleDownUI(getInstrumentation());
CriteriaHelper.pollInstrumentationThread(new Criteria("Should be in overview mode") {
@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)
@RetryOnFailure
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)
@RetryOnFailure
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)
@RetryOnFailure
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)
@RetryOnFailure
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)
@RetryOnFailure
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);
}
@MediumTest
@Feature({"Android-TabSwitcher"})
@Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE)
@RetryOnFailure
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);
}
@MediumTest
@Feature({"Android-TabSwitcher"})
@Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE)
@RetryOnFailure
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) {
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 {
CriteriaHelper.pollUiThread(
new Criteria("Static Layout never selected after side swipe") {
@Override
public boolean isSatisfied() {
CompositorViewHolder compositorViewHolder = (CompositorViewHolder)
getActivity().findViewById(R.id.compositor_view_holder);
LayoutManager layoutManager = compositorViewHolder.getLayoutManager();
return layoutManager.getActiveLayout() instanceof StaticLayout;
}
});
}
/**
* Test that swipes and tab transitions are not causing URL bar to be focused.
*/
@MediumTest
@Restriction({ChromeRestriction.RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE})
@Feature({"Android-TabSwitcher"})
@RetryOnFailure
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);
}
});
CriteriaHelper.pollUiThread(
new Criteria("Layout still requesting Tab Android view be attached") {
@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();
}
});
CriteriaHelper.pollUiThread(
new Criteria("Layout not requesting Tab Android view be attached") {
@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)
@RetryOnFailure
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({ChromeRestriction.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"})
@RetryOnFailure
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"})
@RetryOnFailure
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);
}
@MediumTest
@Feature({"Android-TabSwitcher"})
@RetryOnFailure
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());
}
@MediumTest
@Feature({"Android-TabSwitcher"})
@RetryOnFailure
public void testIncognitoTabsNotRestoredAfterSwipe() throws Exception {
newIncognitoTabFromMenu();
File tabStateDir = TabbedModeTabPersistencePolicy.getOrCreateTabbedModeStateDirectory();
TabModel normalModel = getActivity().getTabModelSelector().getModel(false);
TabModel incognitoModel = getActivity().getTabModelSelector().getModel(true);
File normalTabFile = new File(tabStateDir,
TabState.getTabStateFilename(
normalModel.getTabAt(normalModel.getCount() - 1).getId(), false));
File incognitoTabFile = new File(tabStateDir,
TabState.getTabStateFilename(incognitoModel.getTabAt(0).getId(), true));
assertFileExists(normalTabFile, true);
assertFileExists(incognitoTabFile, true);
// Although we're destroying the activity, the Application will still live on since its in
// the same process as this test.
ApplicationTestUtils.finishActivity(getActivity());
// Activity will be started without a savedInstanceState.
startMainActivityOnBlankPage();
assertFileExists(normalTabFile, true);
assertFileExists(incognitoTabFile, false);
}
@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;
}
startMainActivityFromLauncher();
}
private void assertFileExists(final File fileToCheck, final boolean expected)
throws InterruptedException {
CriteriaHelper.pollInstrumentationThread(
Criteria.equals(expected, new Callable<Boolean>() {
@Override
public Boolean call() {
return fileToCheck.exists();
}
}));
}
}