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

// Copyright 2013 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 androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;

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.Feature;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.tabmodel.TabClosureParams;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.chrome.test.R;
import org.chromium.chrome.test.batch.BlankCTATabInitialStateRule;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.test.util.TestCallbackHelperContainer;
import org.chromium.net.test.EmbeddedTestServer;
import org.chromium.ui.modaldialog.ModalDialogProperties;
import org.chromium.ui.modaldialog.ModalDialogProperties.ButtonType;
import org.chromium.ui.modelutil.PropertyModel;

import java.util.concurrent.TimeoutException;

/**
 * Integration tests verifying that form resubmission dialogs are correctly displayed and handled.
 */
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
@Batch(Batch.PER_CLASS)
public class RepostFormWarningTest {
    // Active tab.

    @ClassRule
    public static ChromeTabbedActivityTestRule sActivityTestRule =
            new ChromeTabbedActivityTestRule();

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

    private Tab mTab;
    // Callback helper that manages waiting for pageloads to finish.
    private TestCallbackHelperContainer mCallbackHelper;

    private EmbeddedTestServer mTestServer;

    @Before
    public void setUp() throws Exception {
        mTab = sActivityTestRule.getActivity().getActivityTab();
        mCallbackHelper = new TestCallbackHelperContainer(mTab.getWebContents());
        mTestServer = sActivityTestRule.getTestServer();
    }

    /** Verifies that the form resubmission warning is not displayed upon first POST navigation. */
    @Test
    @MediumTest
    @Feature({"Navigation"})
    public void testFormFirstNavigation() throws Throwable {
        // Load the url posting data for the first time.
        postNavigation();
        mCallbackHelper.getOnPageFinishedHelper().waitForCallback(0);
        InstrumentationRegistry.getInstrumentation().waitForIdleSync();

        // Verify that the form resubmission warning was not shown.
        waitForNoReportFormWarningDialog();
    }

    /** Verifies that confirming the form reload performs the reload. */
    @Test
    @MediumTest
    @Feature({"Navigation"})
    public void testFormResubmissionContinue() throws Throwable {
        // Load the url posting data for the first time.
        postNavigation();
        mCallbackHelper.getOnPageFinishedHelper().waitForCallback(0);

        // Trigger a reload and wait for the warning to be displayed.
        reload();
        PropertyModel dialog = waitForRepostFormWarningDialog();

        // Click "Continue" and verify that the page is reloaded.
        clickButton(dialog, ModalDialogProperties.ButtonType.POSITIVE);
        mCallbackHelper.getOnPageFinishedHelper().waitForCallback(1);

        // Verify that the reference to the dialog in RepostFormWarningDialog was cleared.
        waitForNoReportFormWarningDialog();
    }

    /**
     * Verifies that cancelling the form reload prevents it from happening. Currently the test waits
     * after the "Cancel" button is clicked to verify that the load was not triggered, which blocks
     * for CallbackHelper's default timeout upon each execution.
     */
    @Test
    @SmallTest
    @Feature({"Navigation"})
    public void testFormResubmissionCancel() throws Throwable {
        // Load the url posting data for the first time.
        postNavigation();
        mCallbackHelper.getOnPageFinishedHelper().waitForCallback(0);

        // Trigger a reload and wait for the warning to be displayed.
        reload();
        PropertyModel dialog = waitForRepostFormWarningDialog();

        // Click "Cancel" and verify that the page is not reloaded.
        clickButton(dialog, ModalDialogProperties.ButtonType.NEGATIVE);
        boolean timedOut = false;
        try {
            mCallbackHelper.getOnPageFinishedHelper().waitForCallback(1);
        } catch (TimeoutException ex) {
            timedOut = true;
        }
        Assert.assertTrue("Page was reloaded despite selecting Cancel.", timedOut);

        // Verify that the reference to the dialog in RepostFormWarningDialog was cleared.
        waitForNoReportFormWarningDialog();
    }

    /** Verifies that destroying the Tab dismisses the form resubmission dialog. */
    @Test
    @SmallTest
    @Feature({"Navigation"})
    public void testFormResubmissionTabDestroyed() throws Throwable {
        // Load the url posting data for the first time.
        postNavigation();
        mCallbackHelper.getOnPageFinishedHelper().waitForCallback(0);

        // Trigger a reload and wait for the warning to be displayed.
        reload();
        waitForRepostFormWarningDialog();

        ThreadUtils.runOnUiThreadBlocking(
                (Runnable)
                        () ->
                                sActivityTestRule
                                        .getActivity()
                                        .getCurrentTabModel()
                                        .closeTabs(
                                                TabClosureParams.closeTab(mTab)
                                                        .allowUndo(false)
                                                        .build()));

        waitForNoReportFormWarningDialog();
    }

    private PropertyModel getCurrentModalDialog() {
        return ThreadUtils.runOnUiThreadBlocking(
                () ->
                        sActivityTestRule
                                .getActivity()
                                .getModalDialogManager()
                                .getCurrentDialogForTest());
    }

    private void waitForNoReportFormWarningDialog() {
        CriteriaHelper.pollUiThread(
                () -> {
                    Criteria.checkThat(
                            "Form resubmission dialog not dismissed correctly",
                            getCurrentModalDialog(),
                            Matchers.nullValue());
                });
    }

    private PropertyModel waitForRepostFormWarningDialog() {
        CriteriaHelper.pollUiThread(
                () -> {
                    PropertyModel dialogModel = getCurrentModalDialog();
                    Criteria.checkThat(
                            "No modal dialog shown", dialogModel, Matchers.notNullValue());
                    Criteria.checkThat(
                            "Modal dialog is not a HTTP post dialog",
                            dialogModel.get(ModalDialogProperties.TITLE),
                            Matchers.is(
                                    sActivityTestRule
                                            .getActivity()
                                            .getString(R.string.http_post_warning_title)));
                });
        return getCurrentModalDialog();
    }

    /** Performs a POST navigation in mTab. */
    private void postNavigation() {
        final String url = "/chrome/test/data/android/test.html";
        final byte[] postData = new byte[] {42};

        InstrumentationRegistry.getInstrumentation()
                .runOnMainSync(
                        () ->
                                mTab.loadUrl(
                                        LoadUrlParams.createLoadHttpPostParams(
                                                mTestServer.getURL(url), postData)));
    }

    /** Reloads mTab. */
    private void reload() {
        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> mTab.reload());
    }

    /** Clicks the given button in the given dialog. */
    private void clickButton(final PropertyModel dialog, final @ButtonType int type) {
        InstrumentationRegistry.getInstrumentation()
                .runOnMainSync(
                        () -> dialog.get(ModalDialogProperties.CONTROLLER).onClick(dialog, type));
    }
}