chromium/chrome/android/javatests/src/org/chromium/chrome/browser/tab/UndoIntegrationTest.java

// Copyright 2015 The Chromium Authors
// 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.tab;

import static org.chromium.base.test.util.Restriction.RESTRICTION_TYPE_NON_LOW_END_DEVICE;
import static org.chromium.ui.test.util.UiRestriction.RESTRICTION_TYPE_TABLET;

import android.os.SystemClock;

import androidx.test.filters.LargeTest;

import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Criteria;
import org.chromium.base.test.util.CriteriaHelper;
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.compositor.overlays.strip.StripLayoutTab;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelUtils;
import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.chrome.test.batch.BlankCTATabInitialStateRule;
import org.chromium.chrome.test.util.TabStripUtils;
import org.chromium.content_public.browser.test.util.DOMUtils;

import java.util.concurrent.TimeoutException;

/** Tests undo and it's interactions with the UI. */
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
@Batch(Batch.PER_CLASS)
public class UndoIntegrationTest {
    @ClassRule
    public static ChromeTabbedActivityTestRule sActivityTestRule =
            new ChromeTabbedActivityTestRule();

    @Rule
    public BlankCTATabInitialStateRule mBlankCTATabInitialStateRule =
            new BlankCTATabInitialStateRule(sActivityTestRule, false);

    private static final String WINDOW_OPEN_BUTTON_URL =
            UrlUtils.encodeHtmlDataUri(
                    "<html>"
                            + "  <head>"
                            + "  <script>"
                            + "    function openWindow() {"
                            + "      window.open('about:blank');"
                            + "    }"
                            + "  </script>"
                            + "  </head>"
                            + "  <body>"
                            + "    <a id=\"link\" onclick=\"setTimeout(openWindow, 500);\">Open</a>"
                            + "  </body>"
                            + "</html>");

    @Before
    public void setUp() throws InterruptedException {
        SnackbarManager.setDurationForTesting(1500);
    }

    /**
     * Test that a tab that is closing can't open other windows.
     *
     * @throws TimeoutException
     */
    @Test
    @LargeTest
    @Restriction(RESTRICTION_TYPE_NON_LOW_END_DEVICE)
    public void testAddNewContentsFromClosingTab() throws TimeoutException {
        // Load in a new tab as Chrome will close if the last tab is closed.
        sActivityTestRule.loadUrlInNewTab(WINDOW_OPEN_BUTTON_URL);

        final TabModel model =
                sActivityTestRule.getActivity().getTabModelSelector().getCurrentModel();
        final Tab tab = TabModelUtils.getCurrentTab(model);

        // Click on the link that will trigger a delayed window popup. If this resolves it will open
        // a second about:blank tab.
        DOMUtils.clickNode(tab.getWebContents(), "link");

        // Attempt to close the tab, which will delay closing until the undo timeout goes away.
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    Assert.assertEquals("Model should have two tabs", 2, model.getCount());
                    TabModelUtils.closeTabById(model, tab.getId(), true);
                    Assert.assertTrue("Tab was not marked as closing", tab.isClosing());
                    Assert.assertTrue(
                            "Tab is not actually closing", model.isClosurePending(tab.getId()));
                });

        // Give the model a chance to process the pending closure.
        CriteriaHelper.pollUiThread(
                () -> {
                    Criteria.checkThat(model.isClosurePending(tab.getId()), Matchers.is(false));
                    Criteria.checkThat(model.getCount(), Matchers.is(1));
                });

        // Validate that the model doesn't contain the original tab or any newly opened tabs.
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    Assert.assertFalse(
                            "Model is still waiting to close the tab",
                            model.isClosurePending(tab.getId()));
                    Assert.assertEquals(
                            "Model has more than the expected about:blank tab",
                            1,
                            model.getCount());
                });
    }

    // Regression test for crbug/1465745.
    @Test
    @LargeTest
    @Restriction(RESTRICTION_TYPE_TABLET)
    public void testTabletCloseTabAndCommitDoesNotCrash() {
        final ChromeTabbedActivity cta = sActivityTestRule.getActivity();
        sActivityTestRule.loadUrlInNewTab("about:blank");
        TabStripUtils.settleDownCompositor(
                TabStripUtils.getStripLayoutHelperManager(cta).getStripLayoutHelper(false));

        TabModel model = cta.getTabModelSelector().getModel(/* isIncognito= */ false);
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    closeTabViaButton(cta, model.getTabAt(1).getId());
                    closeTabViaButton(cta, model.getTabAt(0).getId());

                    model.commitAllTabClosures();
                });
    }

    private void closeTabViaButton(ChromeTabbedActivity cta, int tabId) {
        final StripLayoutTab tab =
                TabStripUtils.findStripLayoutTab(cta, /* isIncognito= */ false, tabId);
        tab.getCloseButton().handleClick(SystemClock.uptimeMillis());
    }
}