chromium/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientShouldOverrideUrlLoadingTest.java

// Copyright 2012 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.android_webview.test;

import static org.chromium.android_webview.test.AwActivityTestRule.SCALED_WAIT_TIMEOUT_MS;
import static org.chromium.android_webview.test.AwActivityTestRule.WAIT_TIMEOUT_MS;

import android.annotation.SuppressLint;
import android.os.Build;
import android.util.Pair;

import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;

import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.UseParametersRunnerFactory;

import org.chromium.android_webview.AwContents;
import org.chromium.android_webview.AwContentsClient;
import org.chromium.android_webview.AwContentsClient.AwWebResourceRequest;
import org.chromium.android_webview.AwSettings;
import org.chromium.android_webview.policy.AwPolicyProvider;
import org.chromium.android_webview.test.TestAwContentsClient.OnReceivedErrorHelper;
import org.chromium.android_webview.test.util.CommonResources;
import org.chromium.android_webview.test.util.JSUtils;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.Criteria;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.base.test.util.CriteriaNotSatisfiedException;
import org.chromium.base.test.util.DoNotBatch;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.MinAndroidSdkLevel;
import org.chromium.components.policy.AbstractAppRestrictionsProvider;
import org.chromium.components.policy.CombinedPolicyProvider;
import org.chromium.components.policy.test.PolicyData;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.NavigationHistory;
import org.chromium.content_public.browser.test.util.TestCallbackHelperContainer.OnEvaluateJavaScriptResultHelper;
import org.chromium.content_public.browser.test.util.TestCallbackHelperContainer.OnPageStartedHelper;
import org.chromium.content_public.common.ContentUrlConstants;
import org.chromium.net.test.util.TestWebServer;
import org.chromium.url.GURL;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

/** Tests for the WebViewClient.shouldOverrideUrlLoading() method. */
@DoNotBatch(reason = "This test class is historically prone to flakes.")
@RunWith(Parameterized.class)
@UseParametersRunnerFactory(AwJUnit4ClassRunnerWithParameters.Factory.class)
public class AwContentsClientShouldOverrideUrlLoadingTest extends AwParameterizedTest {
    @Rule public AwActivityTestRule mActivityTestRule;

    private static final String DATA_URL = "data:text/html,<div/>";
    private static final String REDIRECT_TARGET_PATH = "/redirect_target.html";
    private static final String TITLE = "TITLE";
    private static final String TAG = "AwContentsClientShouldOverrideUrlLoadingTest";
    private static final String sEnterpriseAuthAppLinkPolicy =
            "com.android.browser:EnterpriseAuthenticationAppLinkPolicy";

    private TestWebServer mWebServer;
    private ShouldOverrideUrlLoadingClient mContentsClient;
    private AwTestContainerView mTestContainerView;
    private AwContents mAwContents;

    private TestAwContentsClient.ShouldOverrideUrlLoadingHelper mShouldOverrideUrlLoadingHelper;

    private static class TestDefaultContentsClient extends ShouldOverrideUrlLoadingClient {
        @Override
        public boolean hasWebViewClient() {
            return false;
        }
    }

    public AwContentsClientShouldOverrideUrlLoadingTest(AwSettingsMutation param) {
        this.mActivityTestRule = new AwActivityTestRule(param.getMutation());
    }

    @Before
    public void setUp() throws Exception {
        mWebServer = TestWebServer.start();
    }

    @After
    public void tearDown() {
        mWebServer.shutdown();
    }

    private static class ShouldOverrideUrlLoadingClient extends TestAwContentsClient {
        private final BlockingQueue<AwWebResourceRequest> mShouldOverrideUrlLoadingQueue =
                new LinkedBlockingQueue<>();
        private final BlockingQueue<String> mOnPageFinishedQueue = new LinkedBlockingQueue<>();

        @Override
        public boolean shouldOverrideUrlLoading(AwWebResourceRequest request) {
            boolean value = super.shouldOverrideUrlLoading(request);
            mShouldOverrideUrlLoadingQueue.offer(request);
            return value;
        }

        @Override
        public void onPageFinished(String url) {
            super.onPageFinished(url);
            mOnPageFinishedQueue.offer(url);
        }

        public AwWebResourceRequest waitForShouldOverrideUrlLoading() throws Exception {
            return AwActivityTestRule.waitForNextQueueElement(mShouldOverrideUrlLoadingQueue);
        }

        public String waitForOnPageFinished() throws Exception {
            return AwActivityTestRule.waitForNextQueueElement(mOnPageFinishedQueue);
        }
    }

    private void standardSetup() {
        setupWithProvidedContentsClient(new ShouldOverrideUrlLoadingClient());
        mShouldOverrideUrlLoadingHelper = mContentsClient.getShouldOverrideUrlLoadingHelper();
    }

    private void setupWithProvidedContentsClient(ShouldOverrideUrlLoadingClient contentsClient) {
        mContentsClient = contentsClient;
        mTestContainerView = mActivityTestRule.createAwTestContainerViewOnMainSync(contentsClient);
        mAwContents = mTestContainerView.getAwContents();
    }

    private void clickOnLinkUsingJs() {
        AwActivityTestRule.enableJavaScriptOnUiThread(mAwContents);
        JSUtils.clickOnLinkUsingJs(
                InstrumentationRegistry.getInstrumentation(),
                mAwContents,
                mContentsClient.getOnEvaluateJavaScriptResultHelper(),
                "link");
    }

    // Since this value is read on the UI thread, it's simpler to set it there too.
    void setShouldOverrideUrlLoadingReturnValueOnUiThread(final boolean value) {
        InstrumentationRegistry.getInstrumentation()
                .runOnMainSync(
                        () ->
                                mShouldOverrideUrlLoadingHelper
                                        .setShouldOverrideUrlLoadingReturnValue(value));
    }

    private String getTestPageCommonHeaders() {
        return "<title>" + TITLE + "</title> ";
    }

    private String makeHtmlPageFrom(String headers, String body) {
        return CommonResources.makeHtmlPageFrom(getTestPageCommonHeaders() + headers, body);
    }

    private String getHtmlForPageWithJsAssignLinkTo(String url) {
        return makeHtmlPageFrom(
                "",
                "<img onclick=\"location.href='"
                        + url
                        + "'\" class=\"big\" id=\"link\" /><p>Text</p>");
    }

    private String getHtmlForPageWithJsReplaceLinkTo(String url) {
        return makeHtmlPageFrom(
                "",
                "<img onclick=\"location.replace('"
                        + url
                        + "');\" class=\"big\" id=\"link\" /><p>Text</p>");
    }

    private String getHtmlForPageWithMetaRefreshRedirectTo(String url) {
        return makeHtmlPageFrom(
                "<meta http-equiv=\"refresh\" content=\"0;url=" + url + "\" />",
                "<div>Meta refresh redirect</div>");
    }

    @SuppressLint("DefaultLocale")
    private String getHtmlForPageWithJsRedirectTo(String url, String method, int timeout) {
        return makeHtmlPageFrom(
                ""
                        + "<script>"
                        + "function doRedirectAssign() {"
                        + "location.href = '"
                        + url
                        + "';"
                        + "} "
                        + "function doRedirectReplace() {"
                        + "location.replace('"
                        + url
                        + "');"
                        + "} "
                        + "</script>",
                String.format(
                        "<script>" + "setTimeout('doRedirect%s()', %d)" + "</script>",
                        method, timeout));
    }

    private String addPageToTestServer(String httpPath, String html) {
        List<Pair<String, String>> headers = new ArrayList<Pair<String, String>>();
        headers.add(Pair.create("Content-Type", "text/html"));
        headers.add(Pair.create("Cache-Control", "no-store"));
        return mWebServer.setResponse(httpPath, html, headers);
    }

    private String createRedirectTargetPage() {
        return addPageToTestServer(
                REDIRECT_TARGET_PATH,
                makeHtmlPageFrom("", "<div>This is the end of the redirect chain</div>"));
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView", "Navigation"})
    public void testNotCalledOnLoadUrl() throws Throwable {
        standardSetup();
        mActivityTestRule.loadDataSync(
                mAwContents,
                mContentsClient.getOnPageFinishedHelper(),
                CommonResources.makeHtmlPageWithSimpleLinkTo(DATA_URL),
                "text/html",
                false);

        Assert.assertEquals(0, mShouldOverrideUrlLoadingHelper.getCallCount());
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView", "Navigation"})
    public void testNotCalledOnReload() throws Throwable {
        standardSetup();
        mActivityTestRule.loadDataSync(
                mAwContents,
                mContentsClient.getOnPageFinishedHelper(),
                CommonResources.makeHtmlPageWithSimpleLinkTo(DATA_URL),
                "text/html",
                false);

        int callCountBeforeReload = mShouldOverrideUrlLoadingHelper.getCallCount();
        mActivityTestRule.reloadSync(mAwContents, mContentsClient.getOnPageFinishedHelper());
        Assert.assertEquals(callCountBeforeReload, mShouldOverrideUrlLoadingHelper.getCallCount());
    }

    private void waitForNavigationRunnableAndAssertTitleChanged(Runnable navigationRunnable)
            throws Exception {
        CallbackHelper onPageFinishedHelper = mContentsClient.getOnPageFinishedHelper();
        final int callCount = onPageFinishedHelper.getCallCount();
        final String oldTitle = mActivityTestRule.getTitleOnUiThread(mAwContents);
        InstrumentationRegistry.getInstrumentation().runOnMainSync(navigationRunnable);
        onPageFinishedHelper.waitForCallback(callCount);
        Assert.assertFalse(oldTitle.equals(mActivityTestRule.getTitleOnUiThread(mAwContents)));
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView", "Navigation"})
    public void testNotCalledOnBackForwardNavigation() throws Throwable {
        standardSetup();
        final String[] pageTitles = new String[] {"page1", "page2", "page3"};

        for (String title : pageTitles) {
            mActivityTestRule.loadDataSync(
                    mAwContents,
                    mContentsClient.getOnPageFinishedHelper(),
                    CommonResources.makeHtmlPageFrom("<title>" + title + "</title>", ""),
                    "text/html",
                    false);
        }
        Assert.assertEquals(0, mShouldOverrideUrlLoadingHelper.getCallCount());

        waitForNavigationRunnableAndAssertTitleChanged(() -> mAwContents.goBack());
        Assert.assertEquals(0, mShouldOverrideUrlLoadingHelper.getCallCount());

        waitForNavigationRunnableAndAssertTitleChanged(() -> mAwContents.goForward());
        Assert.assertEquals(0, mShouldOverrideUrlLoadingHelper.getCallCount());

        waitForNavigationRunnableAndAssertTitleChanged(() -> mAwContents.goBackOrForward(-2));
        Assert.assertEquals(0, mShouldOverrideUrlLoadingHelper.getCallCount());

        waitForNavigationRunnableAndAssertTitleChanged(() -> mAwContents.goBackOrForward(1));
        Assert.assertEquals(0, mShouldOverrideUrlLoadingHelper.getCallCount());
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView", "Navigation"})
    public void testCantBlockLoads() throws Throwable {
        standardSetup();
        setShouldOverrideUrlLoadingReturnValueOnUiThread(true);

        mActivityTestRule.loadDataSync(
                mAwContents,
                mContentsClient.getOnPageFinishedHelper(),
                CommonResources.makeHtmlPageWithSimpleLinkTo(getTestPageCommonHeaders(), DATA_URL),
                "text/html",
                false);

        Assert.assertEquals(TITLE, mActivityTestRule.getTitleOnUiThread(mAwContents));
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView", "Navigation"})
    public void testCalledBeforeOnPageStarted() throws Throwable {
        standardSetup();
        OnPageStartedHelper onPageStartedHelper = mContentsClient.getOnPageStartedHelper();

        mActivityTestRule.loadDataSync(
                mAwContents,
                mContentsClient.getOnPageFinishedHelper(),
                CommonResources.makeHtmlPageWithSimpleLinkTo(
                        ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL),
                "text/html",
                false);

        final int shouldOverrideUrlLoadingCallCount =
                mShouldOverrideUrlLoadingHelper.getCallCount();
        final int onPageStartedCallCount = onPageStartedHelper.getCallCount();
        setShouldOverrideUrlLoadingReturnValueOnUiThread(true);
        clickOnLinkUsingJs();

        mShouldOverrideUrlLoadingHelper.waitForCallback(shouldOverrideUrlLoadingCallCount);
        Assert.assertEquals(onPageStartedCallCount, onPageStartedHelper.getCallCount());
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView", "Navigation"})
    public void testDoesNotCauseOnReceivedError() throws Throwable {
        standardSetup();
        OnReceivedErrorHelper onReceivedErrorHelper = mContentsClient.getOnReceivedErrorHelper();
        final int onReceivedErrorCount = onReceivedErrorHelper.getCallCount();

        mActivityTestRule.loadDataSync(
                mAwContents,
                mContentsClient.getOnPageFinishedHelper(),
                CommonResources.makeHtmlPageWithSimpleLinkTo(
                        ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL),
                "text/html",
                false);

        final int shouldOverrideUrlLoadingCallCount =
                mShouldOverrideUrlLoadingHelper.getCallCount();
        setShouldOverrideUrlLoadingReturnValueOnUiThread(true);
        clickOnLinkUsingJs();
        mShouldOverrideUrlLoadingHelper.waitForCallback(shouldOverrideUrlLoadingCallCount);
        setShouldOverrideUrlLoadingReturnValueOnUiThread(false);

        // After we load this URL we're certain that any in-flight callbacks for the previous
        // navigation have been delivered.
        mActivityTestRule.loadUrlSync(
                mAwContents, mContentsClient.getOnPageFinishedHelper(), DATA_URL);

        Assert.assertEquals(onReceivedErrorCount, onReceivedErrorHelper.getCallCount());
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView", "Navigation"})
    public void testNotCalledForAnchorNavigations() throws Throwable {
        doTestNotCalledForAnchorNavigations(false);
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView", "Navigation"})
    public void testNotCalledForAnchorNavigationsWithNonHierarchicalScheme() throws Throwable {
        doTestNotCalledForAnchorNavigations(true);
    }

    private void doTestNotCalledForAnchorNavigations(boolean useLoadData) throws Throwable {
        standardSetup();

        final String anchorLinkPath = "/anchor_link.html";
        final String anchorLinkUrl = mWebServer.getResponseUrl(anchorLinkPath);
        addPageToTestServer(
                anchorLinkPath,
                CommonResources.makeHtmlPageWithSimpleLinkTo(anchorLinkUrl + "#anchor"));

        if (useLoadData) {
            final String html =
                    CommonResources.makeHtmlPageWithSimpleLinkTo("#anchor").replace("#", "%23");
            // Loading the html via a data URI requires us to encode '#' symbols as '%23'.
            mActivityTestRule.loadDataSync(
                    mAwContents,
                    mContentsClient.getOnPageFinishedHelper(),
                    html,
                    "text/html",
                    false);
        } else {
            mActivityTestRule.loadUrlSync(
                    mAwContents, mContentsClient.getOnPageFinishedHelper(), anchorLinkUrl);
        }

        final int shouldOverrideUrlLoadingCallCount =
                mShouldOverrideUrlLoadingHelper.getCallCount();

        clickOnLinkUsingJs();

        // After we load this URL we're certain that any in-flight callbacks for the previous
        // navigation have been delivered.
        mActivityTestRule.loadUrlSync(
                mAwContents,
                mContentsClient.getOnPageFinishedHelper(),
                ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL);

        Assert.assertEquals(
                shouldOverrideUrlLoadingCallCount, mShouldOverrideUrlLoadingHelper.getCallCount());
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView", "Navigation"})
    public void testCalledWhenLinkClicked() throws Throwable {
        standardSetup();

        mActivityTestRule.loadDataSync(
                mAwContents,
                mContentsClient.getOnPageFinishedHelper(),
                CommonResources.makeHtmlPageWithSimpleLinkTo(
                        ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL),
                "text/html",
                false);

        int callCount = mShouldOverrideUrlLoadingHelper.getCallCount();

        clickOnLinkUsingJs();

        mShouldOverrideUrlLoadingHelper.waitForCallback(callCount);
        Assert.assertEquals(
                ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL,
                mShouldOverrideUrlLoadingHelper.getShouldOverrideUrlLoadingUrl());
        Assert.assertFalse(mShouldOverrideUrlLoadingHelper.isRedirect());
        Assert.assertFalse(mShouldOverrideUrlLoadingHelper.hasUserGesture());
        Assert.assertTrue(mShouldOverrideUrlLoadingHelper.isOutermostMainFrame());
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView", "Navigation"})
    public void testCalledWhenTopLevelAboutBlankNavigation() throws Throwable {
        standardSetup();

        final String httpPath = "/page_with_about_blank_navigation";
        final String httpPathOnServer = mWebServer.getResponseUrl(httpPath);
        addPageToTestServer(
                httpPath,
                CommonResources.makeHtmlPageWithSimpleLinkTo(
                        ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL));

        mActivityTestRule.loadUrlSync(
                mAwContents, mContentsClient.getOnPageFinishedHelper(), httpPathOnServer);

        int callCount = mShouldOverrideUrlLoadingHelper.getCallCount();

        clickOnLinkUsingJs();

        mShouldOverrideUrlLoadingHelper.waitForCallback(callCount);
        Assert.assertEquals(
                ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL,
                mShouldOverrideUrlLoadingHelper.getShouldOverrideUrlLoadingUrl());
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView", "Navigation"})
    public void testCalledWhenSelfLinkClicked() throws Throwable {
        standardSetup();

        final String httpPath = "/page_with_link_to_self.html";
        final String httpPathOnServer = mWebServer.getResponseUrl(httpPath);
        addPageToTestServer(
                httpPath, CommonResources.makeHtmlPageWithSimpleLinkTo(httpPathOnServer));

        mActivityTestRule.loadUrlSync(
                mAwContents, mContentsClient.getOnPageFinishedHelper(), httpPathOnServer);

        int callCount = mShouldOverrideUrlLoadingHelper.getCallCount();

        clickOnLinkUsingJs();

        mShouldOverrideUrlLoadingHelper.waitForCallback(callCount);
        Assert.assertEquals(
                httpPathOnServer, mShouldOverrideUrlLoadingHelper.getShouldOverrideUrlLoadingUrl());
        Assert.assertFalse(mShouldOverrideUrlLoadingHelper.isRedirect());
        Assert.assertFalse(mShouldOverrideUrlLoadingHelper.hasUserGesture());
        Assert.assertTrue(mShouldOverrideUrlLoadingHelper.isOutermostMainFrame());
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView", "Navigation"})
    public void testCalledWhenNavigatingFromJavaScriptUsingAssign() throws Throwable {
        standardSetup();
        AwActivityTestRule.enableJavaScriptOnUiThread(mAwContents);

        final String redirectTargetUrl = createRedirectTargetPage();
        mActivityTestRule.loadDataSync(
                mAwContents,
                mContentsClient.getOnPageFinishedHelper(),
                getHtmlForPageWithJsAssignLinkTo(redirectTargetUrl),
                "text/html",
                false);

        int callCount = mShouldOverrideUrlLoadingHelper.getCallCount();
        clickOnLinkUsingJs();
        mShouldOverrideUrlLoadingHelper.waitForCallback(callCount);
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView", "Navigation"})
    public void testCalledWhenNavigatingFromJavaScriptUsingReplace() throws Throwable {
        standardSetup();
        AwActivityTestRule.enableJavaScriptOnUiThread(mAwContents);

        final String redirectTargetUrl = createRedirectTargetPage();
        mActivityTestRule.loadDataSync(
                mAwContents,
                mContentsClient.getOnPageFinishedHelper(),
                getHtmlForPageWithJsReplaceLinkTo(redirectTargetUrl),
                "text/html",
                false);

        int callCount = mShouldOverrideUrlLoadingHelper.getCallCount();
        clickOnLinkUsingJs();
        mShouldOverrideUrlLoadingHelper.waitForCallback(callCount);
        // It's not a server-side redirect.
        Assert.assertFalse(mShouldOverrideUrlLoadingHelper.isRedirect());
        Assert.assertFalse(mShouldOverrideUrlLoadingHelper.hasUserGesture());
        Assert.assertTrue(mShouldOverrideUrlLoadingHelper.isOutermostMainFrame());
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView", "Navigation"})
    public void testPassesCorrectUrl() throws Throwable {
        standardSetup();

        final String redirectTargetUrl = createRedirectTargetPage();
        mActivityTestRule.loadDataSync(
                mAwContents,
                mContentsClient.getOnPageFinishedHelper(),
                CommonResources.makeHtmlPageWithSimpleLinkTo(redirectTargetUrl),
                "text/html",
                false);

        int callCount = mShouldOverrideUrlLoadingHelper.getCallCount();
        clickOnLinkUsingJs();
        mShouldOverrideUrlLoadingHelper.waitForCallback(callCount);
        Assert.assertEquals(
                redirectTargetUrl,
                mShouldOverrideUrlLoadingHelper.getShouldOverrideUrlLoadingUrl());
        // It's not a server-side redirect.
        Assert.assertFalse(mShouldOverrideUrlLoadingHelper.isRedirect());
        Assert.assertFalse(mShouldOverrideUrlLoadingHelper.hasUserGesture());
        Assert.assertTrue(mShouldOverrideUrlLoadingHelper.isOutermostMainFrame());
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView", "Navigation"})
    public void testCanIgnoreLoading() throws Throwable {
        standardSetup();

        final String redirectTargetUrl = createRedirectTargetPage();
        final String pageWithLinkToIgnorePath = "/page_with_link_to_ignore.html";
        final String pageWithLinkToIgnoreUrl =
                addPageToTestServer(
                        pageWithLinkToIgnorePath,
                        CommonResources.makeHtmlPageWithSimpleLinkTo(redirectTargetUrl));
        final String synchronizationPath = "/sync.html";
        final String synchronizationUrl =
                addPageToTestServer(
                        synchronizationPath,
                        CommonResources.makeHtmlPageWithSimpleLinkTo(redirectTargetUrl));

        mActivityTestRule.loadUrlSync(
                mAwContents, mContentsClient.getOnPageFinishedHelper(), pageWithLinkToIgnoreUrl);

        setShouldOverrideUrlLoadingReturnValueOnUiThread(true);

        int callCount = mShouldOverrideUrlLoadingHelper.getCallCount();
        clickOnLinkUsingJs();
        // Some time around here true should be returned from the shouldOverrideUrlLoading
        // callback causing the navigation caused by calling clickOnLinkUsingJs to be ignored.
        // We validate this by checking which pages were loaded on the server.
        mShouldOverrideUrlLoadingHelper.waitForCallback(callCount);

        setShouldOverrideUrlLoadingReturnValueOnUiThread(false);

        mActivityTestRule.loadUrlSync(
                mAwContents, mContentsClient.getOnPageFinishedHelper(), synchronizationUrl);

        Assert.assertEquals(1, mWebServer.getRequestCount(pageWithLinkToIgnorePath));
        Assert.assertEquals(1, mWebServer.getRequestCount(synchronizationPath));
        Assert.assertEquals(0, mWebServer.getRequestCount(REDIRECT_TARGET_PATH));
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView", "Navigation"})
    public void testCalledForUnsupportedSchemes() throws Throwable {
        standardSetup();
        final String unsupportedSchemeUrl = "foobar://resource/1";
        mActivityTestRule.loadDataSync(
                mAwContents,
                mContentsClient.getOnPageFinishedHelper(),
                CommonResources.makeHtmlPageWithSimpleLinkTo(unsupportedSchemeUrl),
                "text/html",
                false);

        int callCount = mShouldOverrideUrlLoadingHelper.getCallCount();
        clickOnLinkUsingJs();

        mShouldOverrideUrlLoadingHelper.waitForCallback(callCount);
        Assert.assertEquals(
                unsupportedSchemeUrl,
                mShouldOverrideUrlLoadingHelper.getShouldOverrideUrlLoadingUrl());
        Assert.assertFalse(mShouldOverrideUrlLoadingHelper.isRedirect());
        Assert.assertFalse(mShouldOverrideUrlLoadingHelper.hasUserGesture());
        Assert.assertTrue(mShouldOverrideUrlLoadingHelper.isOutermostMainFrame());
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView", "Navigation"})
    public void testNotCalledForPostNavigations() throws Throwable {
        // The reason POST requests are excluded is BUG 155250.
        standardSetup();

        final String redirectTargetUrl = createRedirectTargetPage();
        final String postLinkUrl =
                addPageToTestServer(
                        "/page_with_post_link.html",
                        CommonResources.makeHtmlPageWithSimplePostFormTo(redirectTargetUrl));

        mActivityTestRule.loadUrlSync(
                mAwContents, mContentsClient.getOnPageFinishedHelper(), postLinkUrl);

        final int shouldOverrideUrlLoadingCallCount =
                mShouldOverrideUrlLoadingHelper.getCallCount();

        Assert.assertEquals(0, mWebServer.getRequestCount(REDIRECT_TARGET_PATH));
        clickOnLinkUsingJs();

        // Wait for the target URL to be fetched from the server.
        AwActivityTestRule.pollInstrumentationThread(
                () -> mWebServer.getRequestCount(REDIRECT_TARGET_PATH) == 1);

        // Since the targetURL was loaded from the test server it means all processing related
        // to dispatching a shouldOverrideUrlLoading callback had finished and checking the call
        // is stable.
        Assert.assertEquals(
                shouldOverrideUrlLoadingCallCount, mShouldOverrideUrlLoadingHelper.getCallCount());
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView", "Navigation"})
    public void testCalledFor302AfterPostNavigations() throws Throwable {
        // The reason POST requests are excluded is BUG 155250.
        standardSetup();

        final String redirectTargetUrl = createRedirectTargetPage();
        final String postToGetRedirectUrl = mWebServer.setRedirect("/302.html", redirectTargetUrl);
        final String postLinkUrl =
                addPageToTestServer(
                        "/page_with_post_link.html",
                        CommonResources.makeHtmlPageWithSimplePostFormTo(postToGetRedirectUrl));

        mActivityTestRule.loadUrlSync(
                mAwContents, mContentsClient.getOnPageFinishedHelper(), postLinkUrl);

        final int shouldOverrideUrlLoadingCallCount =
                mShouldOverrideUrlLoadingHelper.getCallCount();
        clickOnLinkUsingJs();
        mShouldOverrideUrlLoadingHelper.waitForCallback(shouldOverrideUrlLoadingCallCount);

        // Wait for the target URL to be fetched from the server.
        AwActivityTestRule.pollInstrumentationThread(
                () -> mWebServer.getRequestCount(REDIRECT_TARGET_PATH) == 1);

        Assert.assertEquals(
                redirectTargetUrl,
                mShouldOverrideUrlLoadingHelper.getShouldOverrideUrlLoadingUrl());
        Assert.assertTrue(mShouldOverrideUrlLoadingHelper.isRedirect());
        Assert.assertFalse(mShouldOverrideUrlLoadingHelper.hasUserGesture());
        Assert.assertTrue(mShouldOverrideUrlLoadingHelper.isOutermostMainFrame());
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView", "Navigation"})
    public void testNotCalledForIframeHttpNavigations() throws Throwable {
        standardSetup();

        final String iframeRedirectTargetUrl = createRedirectTargetPage();
        final String iframeRedirectUrl =
                mWebServer.setRedirect("/302.html", iframeRedirectTargetUrl);
        final String pageWithIframeUrl =
                addPageToTestServer(
                        "/iframe_intercept.html",
                        makeHtmlPageFrom("", "<iframe src=\"" + iframeRedirectUrl + "\" />"));

        final int shouldOverrideUrlLoadingCallCount =
                mShouldOverrideUrlLoadingHelper.getCallCount();

        Assert.assertEquals(0, mWebServer.getRequestCount(REDIRECT_TARGET_PATH));
        mActivityTestRule.loadUrlSync(
                mAwContents, mContentsClient.getOnPageFinishedHelper(), pageWithIframeUrl);

        // Wait for the redirect target URL to be fetched from the server.
        AwActivityTestRule.pollInstrumentationThread(
                () -> mWebServer.getRequestCount(REDIRECT_TARGET_PATH) == 1);

        Assert.assertEquals(
                shouldOverrideUrlLoadingCallCount, mShouldOverrideUrlLoadingHelper.getCallCount());
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView", "Navigation"})
    public void testCalledForIframeUnsupportedSchemeNavigations() throws Throwable {
        standardSetup();

        final String unsupportedSchemeUrl = "foobar://resource/1";
        final String pageWithIframeUrl =
                addPageToTestServer(
                        "/iframe_intercept.html",
                        makeHtmlPageFrom("", "<iframe src=\"" + unsupportedSchemeUrl + "\" />"));

        final int shouldOverrideUrlLoadingCallCount =
                mShouldOverrideUrlLoadingHelper.getCallCount();

        mActivityTestRule.loadUrlSync(
                mAwContents, mContentsClient.getOnPageFinishedHelper(), pageWithIframeUrl);

        mShouldOverrideUrlLoadingHelper.waitForCallback(shouldOverrideUrlLoadingCallCount);
        Assert.assertEquals(
                unsupportedSchemeUrl,
                mShouldOverrideUrlLoadingHelper.getShouldOverrideUrlLoadingUrl());
        Assert.assertFalse(mShouldOverrideUrlLoadingHelper.isRedirect());
        Assert.assertFalse(mShouldOverrideUrlLoadingHelper.hasUserGesture());
        Assert.assertFalse(mShouldOverrideUrlLoadingHelper.isOutermostMainFrame());
    }

    private void waitForRedirectsToFinish(String redirectUrl, String redirectTarget) {
        // Drain the onPageFinished callback queue until we get the final expected onPageFinished
        // callback.
        CriteriaHelper.pollInstrumentationThread(
                () -> {
                    String url = mContentsClient.waitForOnPageFinished();
                    if (redirectUrl.equals(url)) {
                        // We will sometimes receive onPageFinished for the first page load (such as
                        // for "delayed" JavaScript redirects), but may not receive this for
                        // "immediate" JavaScript redirects or 302 server-side redirects.
                        return false;
                    }
                    if (redirectTarget.equals(url)) {
                        // This should be the last onPageFinished callback.
                        return true;
                    }
                    Assert.fail("Received an unexpected URL from onPageFinished: " + url);
                    return false; // This is unreached, but the compiler still needs a return value.
                },
                AwActivityTestRule.WAIT_TIMEOUT_MS,
                AwActivityTestRule.CHECK_INTERVAL);
    }

    /**
     * Worker method for the various redirect tests.
     *
     * <p>Calling this will first load the redirect URL built from redirectFilePath, query and
     * locationFilePath and assert that we get a override callback for the destination. The second
     * part of the test loads a page that contains a link which points at the redirect URL. We
     * expect two callbacks - one for the redirect link and another for the destination.
     */
    private void doTestCalledOnRedirect(
            String redirectUrl, String redirectTarget, boolean serverSideRedirect)
            throws Throwable {
        standardSetup();
        final String pageTitle = "doTestCalledOnRedirect page";
        final String pageWithLinkToRedirectUrl =
                addPageToTestServer(
                        "/page_with_link_to_redirect.html",
                        CommonResources.makeHtmlPageWithSimpleLinkTo(
                                "<title>" + pageTitle + "</title>", redirectUrl));
        AwActivityTestRule.enableJavaScriptOnUiThread(mAwContents);

        // There is a slight difference between navigations caused by calling load and navigations
        // caused by clicking on a link:
        //
        //  * when using load the navigation is treated as if it came from the URL bar (has the
        //    navigation type TYPED, doesn't have the has_user_gesture flag); thus the navigation
        //    itself is not reported via shouldOverrideUrlLoading, but then if it has caused a
        //    redirect, the redirect itself is reported;
        //
        //  * when clicking on a link the navigation has the LINK type and has_user_gesture depends
        //    on whether it was a real click done by the user, or has it been done by JS; on click,
        //    both the initial navigation and the redirect are reported via
        //    shouldOverrideUrlLoading.
        mActivityTestRule.loadUrlSync(
                mAwContents, mContentsClient.getOnPageFinishedHelper(), redirectUrl);
        AwWebResourceRequest request = mContentsClient.waitForShouldOverrideUrlLoading();
        Assert.assertEquals(redirectTarget, request.url);
        Assert.assertEquals(serverSideRedirect, request.isRedirect);
        Assert.assertFalse(request.hasUserGesture);
        Assert.assertTrue(request.isOutermostMainFrame);
        waitForRedirectsToFinish(redirectUrl, redirectTarget);

        // Test clicking with JS, hasUserGesture must be false.
        int indirectLoadCallCount = mShouldOverrideUrlLoadingHelper.getCallCount();
        mActivityTestRule.loadUrlSync(
                mAwContents, mContentsClient.getOnPageFinishedHelper(), pageWithLinkToRedirectUrl);
        // This assertion is redundant because loadUrlSync already waits for onPageFinished,
        // however we still need to call waitForOnPageFinished() in order to drain the queue.
        Assert.assertEquals(
                "Expected onPageFinished for pageWithLinkToRedirectUrl",
                pageWithLinkToRedirectUrl,
                mContentsClient.waitForOnPageFinished());
        Assert.assertEquals(
                "shouldOverrideUrlLoading should not be invoked during loadUrlSync",
                indirectLoadCallCount,
                mShouldOverrideUrlLoadingHelper.getCallCount());

        clickOnLinkUsingJs();

        request = mContentsClient.waitForShouldOverrideUrlLoading();
        Assert.assertEquals(redirectUrl, request.url);
        Assert.assertFalse(request.isRedirect);
        Assert.assertFalse(request.hasUserGesture);
        Assert.assertTrue(request.isOutermostMainFrame);

        request = mContentsClient.waitForShouldOverrideUrlLoading();
        Assert.assertEquals(redirectTarget, request.url);
        Assert.assertEquals(serverSideRedirect, request.isRedirect);
        Assert.assertFalse(request.hasUserGesture);
        Assert.assertTrue(request.isOutermostMainFrame);
        waitForRedirectsToFinish(redirectUrl, redirectTarget);

        indirectLoadCallCount = mShouldOverrideUrlLoadingHelper.getCallCount();
        mActivityTestRule.loadUrlSync(
                mAwContents, mContentsClient.getOnPageFinishedHelper(), pageWithLinkToRedirectUrl);
        // This assertion is redundant because loadUrlSync already waits for onPageFinished,
        // however we still need to call waitForOnPageFinished() in order to drain the queue.
        Assert.assertEquals(
                "Expected onPageFinished for pageWithLinkToRedirectUrl",
                pageWithLinkToRedirectUrl,
                mContentsClient.waitForOnPageFinished());
        mActivityTestRule.pollUiThread(() -> mAwContents.getTitle().equals(pageTitle));
        Assert.assertEquals(
                "shouldOverrideUrlLoading should not be invoked during loadUrlSync",
                indirectLoadCallCount,
                mShouldOverrideUrlLoadingHelper.getCallCount());

        // Simulate touch, hasUserGesture must be true only on the first call.
        JSUtils.clickNodeWithUserGesture(mAwContents.getWebContents(), "link");

        request = mContentsClient.waitForShouldOverrideUrlLoading();
        Assert.assertEquals(redirectUrl, request.url);
        Assert.assertFalse(request.isRedirect);
        Assert.assertTrue(request.hasUserGesture);
        Assert.assertTrue(request.isOutermostMainFrame);

        request = mContentsClient.waitForShouldOverrideUrlLoading();
        Assert.assertEquals(redirectTarget, request.url);
        Assert.assertEquals(serverSideRedirect, request.isRedirect);
        Assert.assertFalse(request.hasUserGesture);
        Assert.assertTrue(request.isOutermostMainFrame);
        waitForRedirectsToFinish(redirectUrl, redirectTarget);
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView", "Navigation"})
    public void testCalledOn302Redirect() throws Throwable {
        final String redirectTargetUrl = createRedirectTargetPage();
        final String redirectUrl = mWebServer.setRedirect("/302.html", redirectTargetUrl);
        doTestCalledOnRedirect(redirectUrl, redirectTargetUrl, true);
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView", "Navigation"})
    public void testCalledOnMetaRefreshRedirect() throws Throwable {
        final String redirectTargetUrl = createRedirectTargetPage();
        final String redirectUrl =
                addPageToTestServer(
                        "/meta_refresh.html",
                        getHtmlForPageWithMetaRefreshRedirectTo(redirectTargetUrl));
        doTestCalledOnRedirect(redirectUrl, redirectTargetUrl, false);
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView", "Navigation"})
    public void testCalledOnJavaScriptLocationImmediateAssignRedirect() throws Throwable {
        final String redirectTargetUrl = createRedirectTargetPage();
        final String redirectUrl =
                addPageToTestServer(
                        "/js_immediate_assign.html",
                        getHtmlForPageWithJsRedirectTo(redirectTargetUrl, "Assign", 0));
        doTestCalledOnRedirect(redirectUrl, redirectTargetUrl, false);
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView", "Navigation"})
    public void testCalledOnJavaScriptLocationImmediateReplaceRedirect() throws Throwable {
        final String redirectTargetUrl = createRedirectTargetPage();
        final String redirectUrl =
                addPageToTestServer(
                        "/js_immediate_replace.html",
                        getHtmlForPageWithJsRedirectTo(redirectTargetUrl, "Replace", 0));
        doTestCalledOnRedirect(redirectUrl, redirectTargetUrl, false);
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView", "Navigation"})
    public void testCalledOnJavaScriptLocationDelayedAssignRedirect() throws Throwable {
        final String redirectTargetUrl = createRedirectTargetPage();
        final String redirectUrl =
                addPageToTestServer(
                        "/js_delayed_assign.html",
                        getHtmlForPageWithJsRedirectTo(redirectTargetUrl, "Assign", 100));
        doTestCalledOnRedirect(redirectUrl, redirectTargetUrl, false);
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView", "Navigation"})
    public void testCalledOnJavaScriptLocationDelayedReplaceRedirect() throws Throwable {
        final String redirectTargetUrl = createRedirectTargetPage();
        final String redirectUrl =
                addPageToTestServer(
                        "/js_delayed_replace.html",
                        getHtmlForPageWithJsRedirectTo(redirectTargetUrl, "Replace", 100));
        doTestCalledOnRedirect(redirectUrl, redirectTargetUrl, false);
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView", "Navigation"})
    public void testDoubleNavigateDoesNotSuppressInitialNavigate() throws Throwable {
        final String jsUrl = "javascript:try{console.log('processed js loadUrl');}catch(e){};";
        standardSetup();

        // Do a double navigagtion, the second being an effective no-op, in quick succession (i.e.
        // without yielding the main thread inbetween).
        int currentCallCount = mContentsClient.getOnPageFinishedHelper().getCallCount();
        InstrumentationRegistry.getInstrumentation()
                .runOnMainSync(
                        () -> {
                            mAwContents.loadUrl(
                                    LoadUrlParams.createLoadDataParams(
                                            CommonResources.makeHtmlPageWithSimpleLinkTo(DATA_URL),
                                            "text/html",
                                            false));
                            mAwContents.loadUrl(new LoadUrlParams(jsUrl));
                        });
        mContentsClient
                .getOnPageFinishedHelper()
                .waitForCallback(currentCallCount, 1, WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);

        Assert.assertEquals(0, mShouldOverrideUrlLoadingHelper.getCallCount());
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView"})
    public void testCallDestroyInCallback() throws Throwable {
        class DestroyInCallbackClient extends ShouldOverrideUrlLoadingClient {
            @Override
            public boolean shouldOverrideUrlLoading(AwContentsClient.AwWebResourceRequest request) {
                mAwContents.destroy();
                return super.shouldOverrideUrlLoading(request);
            }
        }

        setupWithProvidedContentsClient(new DestroyInCallbackClient());
        mShouldOverrideUrlLoadingHelper = mContentsClient.getShouldOverrideUrlLoadingHelper();

        OnReceivedErrorHelper onReceivedErrorHelper = mContentsClient.getOnReceivedErrorHelper();
        int onReceivedErrorCount = onReceivedErrorHelper.getCallCount();

        mActivityTestRule.loadDataSync(
                mAwContents,
                mContentsClient.getOnPageFinishedHelper(),
                CommonResources.makeHtmlPageWithSimpleLinkTo(
                        ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL),
                "text/html",
                false);

        int shouldOverrideUrlLoadingCallCount = mShouldOverrideUrlLoadingHelper.getCallCount();
        setShouldOverrideUrlLoadingReturnValueOnUiThread(true);
        clickOnLinkUsingJs();
        mShouldOverrideUrlLoadingHelper.waitForCallback(shouldOverrideUrlLoadingCallCount);

        mActivityTestRule.pollUiThread(() -> AwContents.getNativeInstanceCount() == 0);
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView", "Navigation"})
    public void testReloadingUrlDoesNotBreakBackForwardList() throws Throwable {
        class ReloadInCallbackClient extends ShouldOverrideUrlLoadingClient {
            @Override
            public boolean shouldOverrideUrlLoading(AwContentsClient.AwWebResourceRequest request) {
                super.shouldOverrideUrlLoading(request);
                mAwContents.loadUrl(request.url);
                return true;
            }
        }

        setupWithProvidedContentsClient(new ReloadInCallbackClient());
        mShouldOverrideUrlLoadingHelper = mContentsClient.getShouldOverrideUrlLoadingHelper();
        int shouldOverrideUrlLoadingCallCount = mShouldOverrideUrlLoadingHelper.getCallCount();

        final String linkUrl =
                addPageToTestServer("/foo.html", "<html><body>hello world</body></html>");
        final String html = CommonResources.makeHtmlPageWithSimpleLinkTo(linkUrl);
        final String firstUrl = addPageToTestServer("/first.html", html);
        CallbackHelper onPageFinishedHelper = mContentsClient.getOnPageFinishedHelper();
        mActivityTestRule.loadUrlSync(mAwContents, onPageFinishedHelper, firstUrl);

        int pageFinishedCount = onPageFinishedHelper.getCallCount();
        clickOnLinkUsingJs();
        onPageFinishedHelper.waitForCallback(pageFinishedCount);
        mShouldOverrideUrlLoadingHelper.waitForCallback(shouldOverrideUrlLoadingCallCount);
        Assert.assertEquals(
                linkUrl, mShouldOverrideUrlLoadingHelper.getShouldOverrideUrlLoadingUrl());

        Assert.assertEquals(new GURL(linkUrl), mAwContents.getUrl());
        Assert.assertTrue("Should have a navigation history", mAwContents.canGoBack());
        NavigationHistory navHistory = mAwContents.getNavigationHistory();
        Assert.assertEquals(2, navHistory.getEntryCount());
        Assert.assertEquals(1, navHistory.getCurrentEntryIndex());
        Assert.assertEquals(linkUrl, navHistory.getEntryAtIndex(1).getUrl().getSpec());

        pageFinishedCount = onPageFinishedHelper.getCallCount();
        shouldOverrideUrlLoadingCallCount = mShouldOverrideUrlLoadingHelper.getCallCount();
        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> mAwContents.goBack());
        onPageFinishedHelper.waitForCallback(pageFinishedCount);
        Assert.assertEquals(
                "Should not invoke shouldOverrideUrlLoading() for history navigation",
                shouldOverrideUrlLoadingCallCount,
                mShouldOverrideUrlLoadingHelper.getCallCount());

        Assert.assertFalse("Should not be able to navigate backward", mAwContents.canGoBack());
        Assert.assertEquals(new GURL(firstUrl), mAwContents.getUrl());
        navHistory = mAwContents.getNavigationHistory();
        Assert.assertEquals(2, navHistory.getEntryCount());
        Assert.assertEquals(0, navHistory.getCurrentEntryIndex());
        Assert.assertEquals(firstUrl, navHistory.getEntryAtIndex(0).getUrl().getSpec());
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView"})
    public void testCallStopAndLoadJsInCallback() throws Throwable {
        final String globalJsVar = "window.testCallStopAndLoadJsInCallback";
        class StopInCallbackClient extends ShouldOverrideUrlLoadingClient {
            @Override
            public boolean shouldOverrideUrlLoading(AwContentsClient.AwWebResourceRequest request) {
                mAwContents.stopLoading();
                mAwContents.loadUrl("javascript:" + globalJsVar + "= 1;");
                return super.shouldOverrideUrlLoading(request);
            }
        }

        setupWithProvidedContentsClient(new StopInCallbackClient());
        mShouldOverrideUrlLoadingHelper = mContentsClient.getShouldOverrideUrlLoadingHelper();

        mActivityTestRule.loadDataSync(
                mAwContents,
                mContentsClient.getOnPageFinishedHelper(),
                CommonResources.makeHtmlPageWithSimpleLinkTo("http://foo.com"),
                "text/html",
                false);

        int shouldOverrideUrlLoadingCallCount = mShouldOverrideUrlLoadingHelper.getCallCount();
        setShouldOverrideUrlLoadingReturnValueOnUiThread(true);
        clickOnLinkUsingJs();
        mShouldOverrideUrlLoadingHelper.waitForCallback(shouldOverrideUrlLoadingCallCount);

        CriteriaHelper.pollInstrumentationThread(
                () -> {
                    try {
                        String actual =
                                JSUtils.executeJavaScriptAndWaitForResult(
                                        InstrumentationRegistry.getInstrumentation(), mAwContents,
                                        mContentsClient.getOnEvaluateJavaScriptResultHelper(),
                                                globalJsVar);
                        Criteria.checkThat(actual, Matchers.is("1"));
                    } catch (Exception e) {
                        throw new CriteriaNotSatisfiedException(e);
                    }
                });
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView"})
    public void testCallLoadInCallback() throws Throwable {
        final String httpPath = "/page_with_about_blank_navigation";
        final String httpPathOnServer = mWebServer.getResponseUrl(httpPath);
        addPageToTestServer(
                httpPath,
                CommonResources.makeHtmlPageWithSimpleLinkTo(
                        getTestPageCommonHeaders(), ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL));
        class StopInCallbackClient extends ShouldOverrideUrlLoadingClient {
            @Override
            public boolean shouldOverrideUrlLoading(AwContentsClient.AwWebResourceRequest request) {
                mAwContents.loadUrl(httpPathOnServer);
                return super.shouldOverrideUrlLoading(request);
            }
        }
        setupWithProvidedContentsClient(new StopInCallbackClient());
        mShouldOverrideUrlLoadingHelper = mContentsClient.getShouldOverrideUrlLoadingHelper();
        mActivityTestRule.loadDataSync(
                mAwContents,
                mContentsClient.getOnPageFinishedHelper(),
                CommonResources.makeHtmlPageWithSimpleLinkTo("http://foo.com"),
                "text/html",
                false);
        int shouldOverrideUrlLoadingCallCount = mShouldOverrideUrlLoadingHelper.getCallCount();
        int onPageFinishedCallCount = mContentsClient.getOnPageFinishedHelper().getCallCount();
        setShouldOverrideUrlLoadingReturnValueOnUiThread(true);
        clickOnLinkUsingJs();
        mShouldOverrideUrlLoadingHelper.waitForCallback(shouldOverrideUrlLoadingCallCount);
        mContentsClient.getOnPageFinishedHelper().waitForCallback(onPageFinishedCallCount);

        CriteriaHelper.pollInstrumentationThread(
                () -> {
                    try {
                        Criteria.checkThat(
                                mActivityTestRule.getTitleOnUiThread(mAwContents),
                                Matchers.is(TITLE));
                    } catch (Exception e) {
                        throw new CriteriaNotSatisfiedException(e);
                    }
                });
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView"})
    public void testNullContentsClientWithServerRedirect() throws Throwable {
        try {
            // The test will fire real intents through the test activity.
            // Need to temporarily suppress startActivity otherwise there will be a
            // handler selection window and the test can't dismiss that.
            mActivityTestRule.getActivity().setIgnoreStartActivity(true);
            final String testUrl =
                    mWebServer.setResponse(
                            "/" + CommonResources.ABOUT_FILENAME,
                            CommonResources.ABOUT_HTML,
                            CommonResources.getTextHtmlHeaders(true));
            setupWithProvidedContentsClient(new TestDefaultContentsClient());
            mActivityTestRule.loadUrlSync(
                    mAwContents, mContentsClient.getOnPageFinishedHelper(), testUrl);

            Assert.assertNull(mActivityTestRule.getActivity().getLastSentIntent());

            // Now the server will redirect path1 to path2. Path2 will load ABOUT_HTML.
            // AwContents should create an intent for the server initiated redirection.
            final String path1 = "/from.html";
            final String path2 = "/to.html";
            final String fromUrl = mWebServer.setRedirect(path1, path2);
            final String toUrl =
                    mWebServer.setResponse(
                            path2,
                            CommonResources.ABOUT_HTML,
                            CommonResources.getTextHtmlHeaders(true));
            mActivityTestRule.loadUrlAsync(mAwContents, fromUrl);

            mActivityTestRule.pollUiThread(
                    () -> mActivityTestRule.getActivity().getLastSentIntent() != null);
            Assert.assertEquals(
                    toUrl,
                    mActivityTestRule.getActivity().getLastSentIntent().getData().toString());
        } finally {
            mActivityTestRule.getActivity().setIgnoreStartActivity(false);
        }
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView"})
    public void testNullContentsClientOpenAboutUrlInWebView() throws Throwable {
        try {
            // If there's a bug in WebView, this may fire real intents through the test activity.
            // Need to temporarily suppress startActivity otherwise there will be a
            // handler selection window and the test can't dismiss that.
            mActivityTestRule.getActivity().setIgnoreStartActivity(true);
            setupWithProvidedContentsClient(new TestDefaultContentsClient());
            AwActivityTestRule.enableJavaScriptOnUiThread(mAwContents);
            final String pageTitle = "Click Title";
            final String htmlWithLink =
                    "<html><title>"
                            + pageTitle
                            + "</title>"
                            + "<body><a id='link' href='"
                            + ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL
                            + "'>Click this!</a></body></html>";
            final String urlWithLink =
                    mWebServer.setResponse(
                            "/html_with_link.html",
                            htmlWithLink,
                            CommonResources.getTextHtmlHeaders(true));

            mActivityTestRule.loadUrlSync(
                    mAwContents, mContentsClient.getOnPageFinishedHelper(), urlWithLink);

            // Clicking on an about:blank link should always navigate to the page directly
            int currentCallCount = mContentsClient.getOnPageFinishedHelper().getCallCount();
            JSUtils.clickNodeWithUserGesture(mAwContents.getWebContents(), "link");
            mContentsClient
                    .getOnPageFinishedHelper()
                    .waitForCallback(currentCallCount, 1, WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);

            Assert.assertEquals(
                    new GURL(ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL), mAwContents.getUrl());
        } finally {
            mActivityTestRule.getActivity().setIgnoreStartActivity(false);
        }
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView"})
    public void testNullContentsClientOpenLink() throws Throwable {
        try {
            // The test will fire real intents through the test activity.
            // Need to temporarily suppress startActivity otherwise there will be a
            // handler selection window and the test can't dismiss that.
            mActivityTestRule.getActivity().setIgnoreStartActivity(true);
            final String testUrl =
                    mWebServer.setResponse(
                            "/" + CommonResources.ABOUT_FILENAME,
                            CommonResources.ABOUT_HTML,
                            CommonResources.getTextHtmlHeaders(true));
            setupWithProvidedContentsClient(new TestDefaultContentsClient());
            AwActivityTestRule.enableJavaScriptOnUiThread(mAwContents);
            final String pageTitle = "Click Title";
            final String htmlWithLink =
                    "<html><title>"
                            + pageTitle
                            + "</title>"
                            + "<body><a id='link' href='"
                            + testUrl
                            + "'>Click this!</a></body></html>";
            final String urlWithLink =
                    mWebServer.setResponse(
                            "/html_with_link.html",
                            htmlWithLink,
                            CommonResources.getTextHtmlHeaders(true));

            mActivityTestRule.loadUrlSync(
                    mAwContents, mContentsClient.getOnPageFinishedHelper(), urlWithLink);
            // Executing JS code that tries to navigate somewhere should not create an intent.
            Assert.assertEquals(
                    "\"" + testUrl + "\"",
                    JSUtils.executeJavaScriptAndWaitForResult(
                            InstrumentationRegistry.getInstrumentation(),
                            mAwContents,
                            new OnEvaluateJavaScriptResultHelper(),
                            "document.location.href='" + testUrl + "'"));
            Assert.assertNull(mActivityTestRule.getActivity().getLastSentIntent());

            // Clicking on a link should create an intent.
            JSUtils.clickNodeWithUserGesture(mAwContents.getWebContents(), "link");
            mActivityTestRule.pollUiThread(
                    () -> mActivityTestRule.getActivity().getLastSentIntent() != null);
            Assert.assertEquals(
                    testUrl,
                    mActivityTestRule.getActivity().getLastSentIntent().getData().toString());
        } finally {
            mActivityTestRule.getActivity().setIgnoreStartActivity(false);
        }
    }

    private void setAppLinkPolicy(final AwPolicyProvider testProvider, String url) {
        final PolicyData[] policies = {
            new PolicyData.Str(sEnterpriseAuthAppLinkPolicy, "[{ \"url\": \"" + url + "\"}]")
        };

        AbstractAppRestrictionsProvider.setTestRestrictions(
                PolicyData.asBundle(Arrays.asList(policies)));

        ThreadUtils.runOnUiThreadBlocking(() -> testProvider.refresh());

        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
    }

    @Test
    @SmallTest
    @MinAndroidSdkLevel(Build.VERSION_CODES.R)
    @Feature({"AndroidWebView"})
    public void testForAuthenticationUrlIntentSent() throws Throwable {
        try {
            standardSetup();
            mActivityTestRule.getActivity().setIgnoreStartActivity(true);
            AwSettings contentSettings = mActivityTestRule.getAwSettingsOnUiThread(mAwContents);

            final AwPolicyProvider testProvider =
                    new AwPolicyProvider(mActivityTestRule.getActivity().getApplicationContext());
            ThreadUtils.runOnUiThreadBlocking(
                    () -> CombinedPolicyProvider.get().registerProvider(testProvider));

            final String authenticationUrl =
                    addPageToTestServer(
                            "/redirect" + REDIRECT_TARGET_PATH,
                            makeHtmlPageFrom(
                                    "", "<div>This is the end of the redirect chain</div>"));
            final String loginUrl = mWebServer.setRedirect("/login.html", authenticationUrl);
            // Set the policy for authentication url.
            setAppLinkPolicy(testProvider, authenticationUrl);

            mActivityTestRule.loadUrlSync(
                    mAwContents, mContentsClient.getOnPageFinishedHelper(), loginUrl);

            mActivityTestRule.pollUiThread(
                    () -> mActivityTestRule.getActivity().getLastSentIntent() != null);
            Assert.assertEquals(
                    authenticationUrl,
                    mActivityTestRule.getActivity().getLastSentIntent().getData().toString());
        } finally {
            mActivityTestRule.getActivity().setIgnoreStartActivity(false);
        }
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView"})
    public void testWithoutPolicyForAuthenticationUrlIntentNotSent() throws Throwable {
        try {
            standardSetup();
            mActivityTestRule.getActivity().setIgnoreStartActivity(true);
            AwSettings contentSettings = mActivityTestRule.getAwSettingsOnUiThread(mAwContents);
            final AwPolicyProvider testProvider =
                    new AwPolicyProvider(mActivityTestRule.getActivity().getApplicationContext());
            ThreadUtils.runOnUiThreadBlocking(
                    () -> CombinedPolicyProvider.get().registerProvider(testProvider));
            final String authenticationUrl =
                    addPageToTestServer(
                            "/redirect" + REDIRECT_TARGET_PATH,
                            makeHtmlPageFrom(
                                    "", "<div>This is the end of the redirect chain</div>"));
            final String loginUrl = mWebServer.setRedirect("/login.html", authenticationUrl);

            mActivityTestRule.loadUrlSync(
                    mAwContents, mContentsClient.getOnPageFinishedHelper(), loginUrl);

            Assert.assertNull(mActivityTestRule.getActivity().getLastSentIntent());
        } finally {
            mActivityTestRule.getActivity().setIgnoreStartActivity(false);
        }
    }

    // Verify popups can open about:blank but no shouldoverrideurloading is received for about:blank
    @Test
    @SmallTest
    @Feature({"AndroidWebView"})
    public void testWindowOpenAboutBlankInPopup() throws Throwable {
        TestAwContentsClient.ShouldOverrideUrlLoadingHelper popupShouldOverrideUrlLoadingHelper =
                createPopUp("about:blank", /* waitForTitle= */ true);
        // Popup is just created, so testing against 0 is true.
        Assert.assertEquals(0, popupShouldOverrideUrlLoadingHelper.getCallCount());
    }

    // Verify popups can open custom scheme and shouldoverrideurlloading is called.
    @Test
    @SmallTest
    @Feature({"AndroidWebView"})
    public void testWindowOpenCustomSchemeUrlInPopup() throws Throwable {
        final String popupPath = "foo://bar";
        verifyShouldOverrideUrlLoadingInPopup(popupPath);
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView"})
    public void testWindowOpenHttpUrlInPopup() throws Throwable {
        final String popupPath = "http://example.com/";
        verifyShouldOverrideUrlLoadingInPopup(popupPath);
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView"})
    public void testWindowOpenHttpUrlInPopupAddsTrailingSlash() throws Throwable {
        final String popupPath = "http://example.com";
        verifyShouldOverrideUrlLoadingInPopup(popupPath, popupPath + "/");
    }

    private static final String BAD_SCHEME = "badscheme://";

    // AwContentsClient handling an invalid network scheme
    private static class BadSchemeClient extends ShouldOverrideUrlLoadingClient {
        CountDownLatch mLatch = new CountDownLatch(1);

        @Override
        public boolean shouldOverrideUrlLoading(AwWebResourceRequest request) {
            if (request.url.startsWith(BAD_SCHEME)) {
                mLatch.countDown();
                return true;
            }
            return false;
        }

        @Override
        public void onReceivedError(AwWebResourceRequest request, AwWebResourceError error) {
            super.onReceivedError(request, error);
            throw new RuntimeException("we should not receive an error code! " + request.url);
        }

        public void waitForLatch() {
            try {
                Assert.assertTrue(mLatch.await(SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView"})
    public void testCalledOnServerRedirectInvalidScheme() throws Throwable {
        BadSchemeClient client = new BadSchemeClient();
        setupWithProvidedContentsClient(client);

        final String path1 = "/from.html";
        final String path2 = BAD_SCHEME + "to.html";
        final String fromUrl = mWebServer.setRedirect(path1, path2);
        final String toUrl =
                mWebServer.setResponse(
                        path2,
                        CommonResources.ABOUT_HTML,
                        CommonResources.getTextHtmlHeaders(true));
        mActivityTestRule.loadUrlAsync(mAwContents, fromUrl);
        client.waitForLatch();
        // Wait for an arbitrary amount of time to ensure onReceivedError is never called.
        Thread.sleep(SCALED_WAIT_TIMEOUT_MS / 3);
    }

    private void verifyShouldOverrideUrlLoadingInPopup(String popupPath) throws Throwable {
        verifyShouldOverrideUrlLoadingInPopup(popupPath, popupPath);
    }

    private void verifyShouldOverrideUrlLoadingInPopup(
            String popupPath, String expectedPathInShouldOVerrideUrlLoading) throws Throwable {
        TestAwContentsClient.ShouldOverrideUrlLoadingHelper popupShouldOverrideUrlLoadingHelper =
                createPopUp(popupPath, /* waitForTitle= */ false);
        Assert.assertEquals(
                expectedPathInShouldOVerrideUrlLoading,
                popupShouldOverrideUrlLoadingHelper.getShouldOverrideUrlLoadingUrl());
        Assert.assertEquals(false, popupShouldOverrideUrlLoadingHelper.isRedirect());
        Assert.assertFalse(popupShouldOverrideUrlLoadingHelper.hasUserGesture());
        Assert.assertTrue(popupShouldOverrideUrlLoadingHelper.isOutermostMainFrame());
    }

    private TestAwContentsClient.ShouldOverrideUrlLoadingHelper createPopUp(
            String popupPath, boolean waitForTitle) throws Throwable {
        standardSetup();
        final String parentPageHtml =
                CommonResources.makeHtmlPageFrom(
                        "",
                        "<script>"
                                + "function tryOpenWindow() {"
                                + "  var newWindow = window.open('"
                                + popupPath
                                + "');"
                                + "}</script>");
        mActivityTestRule.triggerPopup(
                mAwContents,
                mContentsClient,
                mWebServer,
                parentPageHtml,
                null,
                null,
                "tryOpenWindow()");

        final TestAwContentsClient popupContentsClient = new TestAwContentsClient();
        final AwTestContainerView popupContainerView =
                mActivityTestRule.createAwTestContainerViewOnMainSync(popupContentsClient);
        final AwContents popupContents = popupContainerView.getAwContents();

        TestAwContentsClient.ShouldOverrideUrlLoadingHelper popupShouldOverrideUrlLoadingHelper =
                popupContentsClient.getShouldOverrideUrlLoadingHelper();
        int currentCallCount = popupShouldOverrideUrlLoadingHelper.getCallCount();

        InstrumentationRegistry.getInstrumentation()
                .runOnMainSync(() -> mAwContents.supplyContentsForPopup(popupContents));

        if (waitForTitle) {
            // Wait for popup to be loaded for about:blank. Turned out that in about:blank
            // navigation to open a popup, both WebviewClient and WebChromeClient callbacks such as
            // OnPageFinished, OnReceivedTitle, onPageStarted, are not called. However,
            // title changes.
            pollTitleAs("about:blank", popupContents);
        } else {
            popupContentsClient
                    .getOnPageFinishedHelper()
                    .waitForCallback(currentCallCount, 1, WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
        }
        return popupShouldOverrideUrlLoadingHelper;
    }

    private void pollTitleAs(final String title, final AwContents awContents) {
        AwActivityTestRule.pollInstrumentationThread(
                () -> title.equals(mActivityTestRule.getTitleOnUiThread(awContents)));
    }
}