| Index: chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/UndoTabModelTest.java
|
| diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/UndoTabModelTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/UndoTabModelTest.java
|
| index 450008e09e7430010450826c8935ed3bfbc5039d..f76c96754495df3695ba4831c7fde708627f362f 100644
|
| --- a/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/UndoTabModelTest.java
|
| +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tabmodel/UndoTabModelTest.java
|
| @@ -4,19 +4,31 @@
|
|
|
| package org.chromium.chrome.browser.tabmodel;
|
|
|
| +import android.annotation.TargetApi;
|
| +import android.os.Build;
|
| import android.test.suitebuilder.annotation.MediumTest;
|
|
|
| +import org.chromium.base.ApplicationStatus;
|
| import org.chromium.base.ThreadUtils;
|
| import org.chromium.base.test.util.FlakyTest;
|
| +import org.chromium.base.test.util.MinAndroidSdkLevel;
|
| import org.chromium.base.test.util.Restriction;
|
| +import org.chromium.base.test.util.UrlUtils;
|
| +import org.chromium.chrome.browser.ChromeTabbedActivity;
|
| +import org.chromium.chrome.browser.ChromeTabbedActivity2;
|
| +import org.chromium.chrome.browser.multiwindow.MultiWindowUtilsTest;
|
| +import org.chromium.chrome.browser.tab.EmptyTabObserver;
|
| import org.chromium.chrome.browser.tab.Tab;
|
| import org.chromium.chrome.browser.tabmodel.TabModel.TabLaunchType;
|
| import org.chromium.chrome.browser.tabmodel.TabModel.TabSelectionType;
|
| import org.chromium.chrome.test.ChromeTabbedActivityTestBase;
|
| import org.chromium.chrome.test.util.ChromeRestriction;
|
| 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_public.browser.LoadUrlParams;
|
|
|
| +import java.util.concurrent.Callable;
|
| import java.util.concurrent.TimeoutException;
|
|
|
| /**
|
| @@ -24,6 +36,8 @@ import java.util.concurrent.TimeoutException;
|
| */
|
| public class UndoTabModelTest extends ChromeTabbedActivityTestBase {
|
| private static final Tab[] EMPTY = new Tab[] { };
|
| + private static final String TEST_URL_0 = UrlUtils.encodeHtmlDataUri("<html>test_url_0.</html>");
|
| + private static final String TEST_URL_1 = UrlUtils.encodeHtmlDataUri("<html>test_url_1.</html>");
|
|
|
| @Override
|
| public void startMainActivity() throws InterruptedException {
|
| @@ -75,6 +89,27 @@ public class UndoTabModelTest extends ChromeTabbedActivityTestBase {
|
| });
|
| }
|
|
|
| + private void createFullyLoadedTabOnUiThread(final ChromeTabbedActivity activity,
|
| + final String url) {
|
| + final CallbackHelper tabCallbackHelper = new CallbackHelper();
|
| + final TabLoadedObserver observer = new TabLoadedObserver(tabCallbackHelper);
|
| +
|
| + ThreadUtils.runOnUiThreadBlocking(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + activity.getTabCreator(false).createNewTab(new LoadUrlParams(url),
|
| + TabLaunchType.FROM_CHROME_UI, null).addObserver(observer);
|
| + }
|
| + });
|
| +
|
| + // Must wait for the page to be fully loaded.
|
| + try {
|
| + tabCallbackHelper.waitForCallback(0);
|
| + } catch (TimeoutException | InterruptedException e) {
|
| + fail("Failed to load the tab.");
|
| + }
|
| + }
|
| +
|
| private void selectTabOnUiThread(final TabModel model, final Tab tab) {
|
| ThreadUtils.runOnUiThreadBlocking(new Runnable() {
|
| @Override
|
| @@ -321,6 +356,44 @@ public class UndoTabModelTest extends ChromeTabbedActivityTestBase {
|
| }
|
| }
|
|
|
| + private void openMostRecentlyClosedTabOnUiThread(final TabModelSelector selector) {
|
| + ThreadUtils.runOnUiThreadBlocking(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + selector.getCurrentModel().openMostRecentlyClosedTab();
|
| + }
|
| + });
|
| + }
|
| +
|
| + // Helper class that notifies when a page load is finished.
|
| + private static class TabLoadedObserver extends EmptyTabObserver {
|
| + private CallbackHelper mLoadedCallback;
|
| +
|
| + public TabLoadedObserver(CallbackHelper loadCallback) {
|
| + super();
|
| + mLoadedCallback = loadCallback;
|
| + }
|
| + @Override
|
| + public void onPageLoadFinished(Tab tab) {
|
| + mLoadedCallback.notifyCalled();
|
| + }
|
| + }
|
| +
|
| + // Helper class that notifies after the tab is closed, and a tab restore service entry has been
|
| + // created in tab restore service.
|
| + private static class TabClosedObserver extends EmptyTabModelObserver {
|
| + private CallbackHelper mTabClosedCallback;
|
| +
|
| + public TabClosedObserver(CallbackHelper closedCallback) {
|
| + mTabClosedCallback = closedCallback;
|
| + }
|
| +
|
| + @Override
|
| + public void didCloseTab(int tabId, boolean incognito) {
|
| + mTabClosedCallback.notifyCalled();
|
| + }
|
| + }
|
| +
|
| /**
|
| * Test undo with a single tab with the following actions/expected states:
|
| * Action Model List Close List Comprehensive List
|
| @@ -1410,4 +1483,204 @@ public class UndoTabModelTest extends ChromeTabbedActivityTestBase {
|
| assertTrue(tab0.isClosing());
|
| assertFalse(tab0.isInitialized());
|
| }
|
| +
|
| + /**
|
| + * Test opening recently closed tabs using the rewound list in Java.
|
| + * @throws InterruptedException
|
| + */
|
| + @MediumTest
|
| + public void testOpenRecentlyClosedTab() throws InterruptedException {
|
| + TabModelSelector selector = getActivity().getTabModelSelector();
|
| + TabModel model = selector.getModel(false);
|
| + ChromeTabCreator tabCreator = getActivity().getTabCreator(false);
|
| +
|
| + createTabOnUiThread(tabCreator);
|
| +
|
| + Tab tab0 = model.getTabAt(0);
|
| + Tab tab1 = model.getTabAt(1);
|
| + Tab[] allTabs = new Tab[]{tab0, tab1};
|
| +
|
| + closeTabOnUiThread(model, tab1, true);
|
| + checkState(model, new Tab[]{tab0}, tab0, new Tab[]{tab1}, allTabs, tab0);
|
| +
|
| + // Ensure tab recovery, and reuse of {@link Tab} objects in Java.
|
| + openMostRecentlyClosedTabOnUiThread(selector);
|
| + checkState(model, allTabs, tab0, EMPTY, allTabs, tab0);
|
| + }
|
| +
|
| + /**
|
| + * Test opening recently closed tab using native tab restore service.
|
| + * @throws InterruptedException
|
| + */
|
| + @MediumTest
|
| + public void testOpenRecentlyClosedTabNative() throws InterruptedException {
|
| + final TabModelSelector selector = getActivity().getTabModelSelector();
|
| + final TabModel model = selector.getModel(false);
|
| +
|
| + // Create new tab and attach observer to listen to loaded event.
|
| + // Native can only successfully recover the tab after a page load has finished and
|
| + // it has navigation history.
|
| + createFullyLoadedTabOnUiThread(getActivity(), TEST_URL_0);
|
| +
|
| + // Close the tab, and commit pending closure.
|
| + assertEquals(model.getCount(), 2);
|
| + closeTabOnUiThread(model, model.getTabAt(1), false);
|
| + assertEquals(1, model.getCount());
|
| + Tab tab0 = model.getTabAt(0);
|
| + Tab[] tabs = new Tab[]{tab0};
|
| + checkState(model, tabs, tab0, EMPTY, tabs, tab0);
|
| +
|
| + // Recover the page.
|
| + openMostRecentlyClosedTabOnUiThread(selector);
|
| +
|
| + assertEquals(2, model.getCount());
|
| + tab0 = model.getTabAt(0);
|
| + Tab tab1 = model.getTabAt(1);
|
| + tabs = new Tab[]{tab0, tab1};
|
| + assertEquals(TEST_URL_0, tab1.getUrl());
|
| + checkState(model, tabs, tab0, EMPTY, tabs, tab0);
|
| + }
|
| +
|
| + /**
|
| + * Test opening recently closed tab when we have multiple windows.
|
| + * | Action | Result
|
| + * 1. Create second window. |
|
| + * 2. Open tab in window 1. |
|
| + * 3. Open tab in window 2. |
|
| + * 4. Close tab in window 1. |
|
| + * 5. Close tab in window 2. |
|
| + * 6. Restore tab. | Tab restored in window 2.
|
| + * 7. Restore tab. | Tab restored in window 1.
|
| + * @throws InterruptedException
|
| + */
|
| + @MediumTest
|
| + @MinAndroidSdkLevel(24)
|
| + @TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
| + public void testOpenRecentlyClosedTabMultiWindow() throws InterruptedException {
|
| + final ChromeTabbedActivity2 secondActivity =
|
| + MultiWindowUtilsTest.createSecondChromeTabbedActivity(getActivity());
|
| +
|
| + // Wait for the second window to be fully initialized.
|
| + CriteriaHelper.pollUiThread(new Criteria() {
|
| + @Override
|
| + public boolean isSatisfied() {
|
| + return secondActivity.getTabModelSelector().isTabStateInitialized();
|
| + }
|
| + });
|
| + // First window context.
|
| + final TabModelSelector firstSelector = getActivity().getTabModelSelector();
|
| + final TabModel firstModel = firstSelector.getModel(false);
|
| +
|
| + // Second window context.
|
| + final TabModel secondModel = secondActivity.getTabModelSelector().getModel(false);
|
| +
|
| + // Create tabs.
|
| + createFullyLoadedTabOnUiThread(getActivity(), TEST_URL_0);
|
| + createFullyLoadedTabOnUiThread(secondActivity, TEST_URL_1);
|
| +
|
| + assertEquals("Unexpected number of tabs in first window.", 2, firstModel.getCount());
|
| + assertEquals("Unexpected number of tabs in second window.", 2, secondModel.getCount());
|
| +
|
| + // Close one tab in the first window.
|
| + closeTabOnUiThread(firstModel, firstModel.getTabAt(1), false);
|
| + assertEquals("Unexpected number of tabs in first window.", 1, firstModel.getCount());
|
| + assertEquals("Unexpected number of tabs in second window.", 2, secondModel.getCount());
|
| +
|
| + // Close one tab in the second window.
|
| + closeTabOnUiThread(secondModel, secondModel.getTabAt(1), false);
|
| + assertEquals("Unexpected number of tabs in first window.", 1, firstModel.getCount());
|
| + assertEquals("Unexpected number of tabs in second window.", 1, secondModel.getCount());
|
| +
|
| + // Restore one tab.
|
| + openMostRecentlyClosedTabOnUiThread(firstSelector);
|
| + assertEquals("Unexpected number of tabs in first window.", 1, firstModel.getCount());
|
| + assertEquals("Unexpected number of tabs in second window.", 2, secondModel.getCount());
|
| +
|
| + // Restore one more tab.
|
| + openMostRecentlyClosedTabOnUiThread(firstSelector);
|
| +
|
| + // Check final states of both windows.
|
| + Tab firstModelTab = firstModel.getTabAt(0);
|
| + Tab secondModelTab = secondModel.getTabAt(0);
|
| + Tab[] firstWindowTabs = new Tab[]{firstModelTab, firstModel.getTabAt(1)};
|
| + Tab[] secondWindowTabs = new Tab[]{secondModelTab, secondModel.getTabAt(1)};
|
| + checkState(firstModel, firstWindowTabs, firstModelTab, EMPTY, firstWindowTabs,
|
| + firstModelTab);
|
| + checkState(secondModel, secondWindowTabs, secondModelTab, EMPTY, secondWindowTabs,
|
| + secondModelTab);
|
| + assertEquals(TEST_URL_0, firstWindowTabs[1].getUrl());
|
| + assertEquals(TEST_URL_1, secondWindowTabs[1].getUrl());
|
| +
|
| + secondActivity.finishAndRemoveTask();
|
| + }
|
| +
|
| + /**
|
| + * Test restoring closed tab from a closed window.
|
| + * | Action | Result
|
| + * 1. Create second window. |
|
| + * 2. Open tab in window 2. |
|
| + * 3. Close tab in window 2. |
|
| + * 4. Close second window. |
|
| + * 5. Restore tab. | Tab restored in first window.
|
| + * @throws InterruptedException
|
| + */
|
| + @MediumTest
|
| + @MinAndroidSdkLevel(24)
|
| + @TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
| + public void testOpenRecentlyClosedTabMultiWindowFallback() throws InterruptedException {
|
| + final ChromeTabbedActivity2 secondActivity =
|
| + MultiWindowUtilsTest.createSecondChromeTabbedActivity(getActivity());
|
| + // Wait for the second window to be fully initialized.
|
| + CriteriaHelper.pollUiThread(new Criteria() {
|
| + @Override
|
| + public boolean isSatisfied() {
|
| + return secondActivity.getTabModelSelector().isTabStateInitialized();
|
| + }
|
| + });
|
| +
|
| + // First window context.
|
| + final TabModelSelector firstSelector = getActivity().getTabModelSelector();
|
| + final TabModel firstModel = firstSelector.getModel(false);
|
| +
|
| + // Second window context.
|
| + final TabModel secondModel = secondActivity.getTabModelSelector().getModel(false);
|
| +
|
| + // Create tab on second window.
|
| + createFullyLoadedTabOnUiThread(secondActivity, TEST_URL_1);
|
| + assertEquals("Window 2 should have 2 tab.", 2, secondModel.getCount());
|
| +
|
| + // Close tab in second window, wait until tab restore service history is created.
|
| + CallbackHelper closedCallback = new CallbackHelper();
|
| + secondModel.addObserver(new TabClosedObserver(closedCallback));
|
| + closeTabOnUiThread(secondModel, secondModel.getTabAt(1), false);
|
| +
|
| + try {
|
| + closedCallback.waitForCallback(0);
|
| + } catch (TimeoutException | InterruptedException e) {
|
| + fail("Failed to close the tab on the second window.");
|
| + }
|
| +
|
| + assertEquals("Window 2 should have 1 tab.", 1, secondModel.getCount());
|
| +
|
| + // Closed the second window. Must wait until it's totally closed.
|
| + int numExpectedActivities = ApplicationStatus.getRunningActivities().size() - 1;
|
| + secondActivity.finishAndRemoveTask();
|
| + CriteriaHelper.pollUiThread(Criteria.equals(numExpectedActivities, new Callable<Integer>() {
|
| + @Override
|
| + public Integer call() {
|
| + return ApplicationStatus.getRunningActivities().size();
|
| + }
|
| + }));
|
| + assertEquals("Window 1 should have 1 tab.", 1, firstModel.getCount());
|
| +
|
| + // Restore closed tab from second window. It should be created in first window.
|
| + openMostRecentlyClosedTabOnUiThread(firstSelector);
|
| + assertEquals("Closed tab in second window should be restored in the first window.", 2,
|
| + firstModel.getCount());
|
| + Tab tab0 = firstModel.getTabAt(0);
|
| + Tab tab1 = firstModel.getTabAt(1);
|
| + Tab[] firstWindowTabs = new Tab[]{tab0, tab1};
|
| + checkState(firstModel, firstWindowTabs, tab0, EMPTY, firstWindowTabs, tab0);
|
| + assertEquals(TEST_URL_1, tab1.getUrl());
|
| + }
|
| }
|
|
|