blob: c67775000c108adb0cc51a6b247909ef6d81a519 [file] [log] [blame]
// Copyright 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.android_webview.test;
import static org.chromium.android_webview.test.AwActivityTestRule.WAIT_TIMEOUT_MS;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.MediumTest;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.android_webview.AwContents;
import org.chromium.android_webview.test.util.CommonResources;
import org.chromium.android_webview.test.util.JSUtils;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.FlakyTest;
import org.chromium.content_public.browser.test.util.TestCallbackHelperContainer;
import org.chromium.content_public.common.ContentUrlConstants;
import org.chromium.net.test.util.TestWebServer;
import java.util.concurrent.CountDownLatch;
/**
* Tests for the ContentViewClient.onPageFinished() method.
*/
@RunWith(AwJUnit4ClassRunner.class)
public class ClientOnPageFinishedTest {
@Rule
public AwActivityTestRule mActivityTestRule = new AwActivityTestRule();
private TestAwContentsClient mContentsClient;
private AwContents mAwContents;
@Before
public void setUp() throws Exception {
setTestAwContentsClient(new TestAwContentsClient());
}
private void setTestAwContentsClient(TestAwContentsClient contentsClient) throws Exception {
mContentsClient = contentsClient;
final AwTestContainerView testContainerView =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
mAwContents = testContainerView.getAwContents();
}
@Test
@MediumTest
@Feature({"AndroidWebView"})
public void testPassesCorrectUrl() throws Throwable {
TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
mContentsClient.getOnPageFinishedHelper();
String html = "<html><body>Simple page.</body></html>";
int currentCallCount = onPageFinishedHelper.getCallCount();
mActivityTestRule.loadDataAsync(mAwContents, html, "text/html", false);
onPageFinishedHelper.waitForCallback(currentCallCount);
Assert.assertEquals("data:text/html," + html, onPageFinishedHelper.getUrl());
}
@Test
@MediumTest
@Feature({"AndroidWebView"})
@FlakyTest(message = "crbug.com/652577")
public void testCalledAfterError() throws Throwable {
class LocalTestClient extends TestAwContentsClient {
private boolean mIsOnReceivedErrorCalled;
private boolean mIsOnPageFinishedCalled;
private boolean mAllowAboutBlank;
@Override
public void onReceivedError(int errorCode, String description, String failingUrl) {
Assert.assertEquals("onReceivedError called twice for " + failingUrl, false,
mIsOnReceivedErrorCalled);
mIsOnReceivedErrorCalled = true;
Assert.assertEquals(
"onPageFinished called before onReceivedError for " + failingUrl, false,
mIsOnPageFinishedCalled);
super.onReceivedError(errorCode, description, failingUrl);
}
@Override
public void onPageFinished(String url) {
if (mAllowAboutBlank && ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL.equals(url)) {
super.onPageFinished(url);
return;
}
Assert.assertEquals(
"onPageFinished called twice for " + url, false, mIsOnPageFinishedCalled);
mIsOnPageFinishedCalled = true;
Assert.assertEquals("onReceivedError not called before onPageFinished for " + url,
true, mIsOnReceivedErrorCalled);
super.onPageFinished(url);
}
void setAllowAboutBlank() {
mAllowAboutBlank = true;
}
}
LocalTestClient testContentsClient = new LocalTestClient();
setTestAwContentsClient(testContentsClient);
TestCallbackHelperContainer.OnReceivedErrorHelper onReceivedErrorHelper =
mContentsClient.getOnReceivedErrorHelper();
TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
mContentsClient.getOnPageFinishedHelper();
String invalidUrl = "http://localhost:7/non_existent";
mActivityTestRule.loadUrlSync(mAwContents, onPageFinishedHelper, invalidUrl);
Assert.assertEquals(invalidUrl, onReceivedErrorHelper.getFailingUrl());
Assert.assertEquals(invalidUrl, onPageFinishedHelper.getUrl());
// Rather than wait a fixed time to see that another onPageFinished callback isn't issued
// we load a valid page. Since callbacks arrive sequentially, this will ensure that
// any extra calls of onPageFinished / onReceivedError will arrive to our client.
testContentsClient.setAllowAboutBlank();
mActivityTestRule.loadUrlSync(
mAwContents, onPageFinishedHelper, ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL);
}
@Test
@MediumTest
@Feature({"AndroidWebView"})
public void testCalledAfterRedirectedUrlIsOverridden() throws Throwable {
/*
* If url1 is redirected url2, and url2 load is overridden, onPageFinished should still be
* called for url2.
* Steps:
* 1. load url1. url1 onPageStarted
* 2. server redirects url1 to url2. url2 onPageStarted
* 3. shouldOverridedUrlLoading called for url2 and returns true
* 4. url2 onPageFinishedCalled
*/
TestWebServer webServer = TestWebServer.start();
try {
final String redirectTargetPath = "/redirect_target.html";
final String redirectTargetUrl = webServer.setResponse(redirectTargetPath,
"<html><body>hello world</body></html>", null);
final String redirectUrl = webServer.setRedirect("/302.html", redirectTargetUrl);
final TestAwContentsClient.ShouldOverrideUrlLoadingHelper urlOverrideHelper =
mContentsClient.getShouldOverrideUrlLoadingHelper();
// Override the load of redirectTargetUrl
urlOverrideHelper.setShouldOverrideUrlLoadingReturnValue(true);
TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
mContentsClient.getOnPageFinishedHelper();
final int currentOnPageFinishedCallCount = onPageFinishedHelper.getCallCount();
mActivityTestRule.loadUrlAsync(mAwContents, redirectUrl);
onPageFinishedHelper.waitForCallback(currentOnPageFinishedCallCount);
// onPageFinished needs to be called for redirectTargetUrl, but not for redirectUrl
Assert.assertEquals(redirectTargetUrl, onPageFinishedHelper.getUrl());
} finally {
webServer.shutdown();
}
}
@Test
@MediumTest
@Feature({"AndroidWebView"})
public void testNotCalledForValidSubresources() throws Throwable {
TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
mContentsClient.getOnPageFinishedHelper();
TestWebServer webServer = TestWebServer.start();
try {
final String testHtml = "<html><head>Header</head><body>Body</body></html>";
final String testPath = "/test.html";
final String syncPath = "/sync.html";
final String testUrl = webServer.setResponse(testPath, testHtml, null);
final String syncUrl = webServer.setResponse(syncPath, testHtml, null);
Assert.assertEquals(0, onPageFinishedHelper.getCallCount());
final int pageWithSubresourcesCallCount = onPageFinishedHelper.getCallCount();
mActivityTestRule.loadDataAsync(mAwContents,
"<html><iframe src=\"" + testUrl + "\" /></html>", "text/html", false);
onPageFinishedHelper.waitForCallback(pageWithSubresourcesCallCount);
// Rather than wait a fixed time to see that an onPageFinished callback isn't issued
// we load another valid page. Since callbacks arrive sequentially if the next callback
// we get is for the synchronizationUrl we know that the previous load did not schedule
// a callback for the iframe.
final int synchronizationPageCallCount = onPageFinishedHelper.getCallCount();
mActivityTestRule.loadUrlAsync(mAwContents, syncUrl);
onPageFinishedHelper.waitForCallback(synchronizationPageCallCount);
Assert.assertEquals(syncUrl, onPageFinishedHelper.getUrl());
Assert.assertEquals(2, onPageFinishedHelper.getCallCount());
} finally {
webServer.shutdown();
}
}
@Test
@MediumTest
@Feature({"AndroidWebView"})
public void testNotCalledForHistoryApi() throws Throwable {
TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
mContentsClient.getOnPageFinishedHelper();
AwActivityTestRule.enableJavaScriptOnUiThread(mAwContents);
TestWebServer webServer = TestWebServer.start();
try {
final String testHtml = "<html><head>Header</head><body>Body</body></html>";
final String testPath = "/test.html";
final String historyPath = "/history.html";
final String syncPath = "/sync.html";
final String testUrl = webServer.setResponse(testPath, testHtml, null);
final String historyUrl = webServer.getResponseUrl(historyPath);
final String syncUrl = webServer.setResponse(syncPath, testHtml, null);
Assert.assertEquals(0, onPageFinishedHelper.getCallCount());
mActivityTestRule.loadUrlSync(mAwContents, onPageFinishedHelper, testUrl);
mActivityTestRule.executeJavaScriptAndWaitForResult(mAwContents, mContentsClient,
"history.pushState(null, null, '" + historyUrl + "');");
// Rather than wait a fixed time to see that an onPageFinished callback isn't issued
// we load another valid page. Since callbacks arrive sequentially if the next callback
// we get is for the synchronizationUrl we know that the previous load did not schedule
// a callback for the iframe.
final int synchronizationPageCallCount = onPageFinishedHelper.getCallCount();
mActivityTestRule.loadUrlAsync(mAwContents, syncUrl);
onPageFinishedHelper.waitForCallback(synchronizationPageCallCount);
Assert.assertEquals(syncUrl, onPageFinishedHelper.getUrl());
Assert.assertEquals(2, onPageFinishedHelper.getCallCount());
} finally {
webServer.shutdown();
}
}
@Test
@MediumTest
@Feature({"AndroidWebView"})
public void testCalledForHrefNavigations() throws Throwable {
doTestOnPageFinishedCalledForHrefNavigations(false);
}
@Test
@MediumTest
@Feature({"AndroidWebView"})
public void testCalledForHrefNavigationsWithBaseUrl() throws Throwable {
doTestOnPageFinishedCalledForHrefNavigations(true);
}
private void doTestOnPageFinishedCalledForHrefNavigations(boolean useBaseUrl) throws Throwable {
TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
mContentsClient.getOnPageFinishedHelper();
TestCallbackHelperContainer.OnPageStartedHelper onPageStartedHelper =
mContentsClient.getOnPageStartedHelper();
AwActivityTestRule.enableJavaScriptOnUiThread(mAwContents);
TestWebServer webServer = TestWebServer.start();
try {
final String testHtml = CommonResources.makeHtmlPageFrom("",
"<a href=\"#anchor\" id=\"link\">anchor</a>");
final String testPath = "/test.html";
final String testUrl = webServer.setResponse(testPath, testHtml, null);
if (useBaseUrl) {
mActivityTestRule.loadDataWithBaseUrlSync(mAwContents, onPageFinishedHelper,
testHtml, "text/html", false, webServer.getBaseUrl(), null);
} else {
mActivityTestRule.loadUrlSync(mAwContents, onPageFinishedHelper, testUrl);
}
int onPageFinishedCallCount = onPageFinishedHelper.getCallCount();
int onPageStartedCallCount = onPageStartedHelper.getCallCount();
JSUtils.clickOnLinkUsingJs(InstrumentationRegistry.getInstrumentation(), mAwContents,
mContentsClient.getOnEvaluateJavaScriptResultHelper(), "link");
onPageFinishedHelper.waitForCallback(onPageFinishedCallCount);
Assert.assertEquals(onPageStartedCallCount, onPageStartedHelper.getCallCount());
onPageFinishedCallCount = onPageFinishedHelper.getCallCount();
onPageStartedCallCount = onPageStartedHelper.getCallCount();
mActivityTestRule.executeJavaScriptAndWaitForResult(
mAwContents, mContentsClient, "window.history.go(-1)");
onPageFinishedHelper.waitForCallback(onPageFinishedCallCount);
Assert.assertEquals(onPageStartedCallCount, onPageStartedHelper.getCallCount());
} finally {
webServer.shutdown();
}
}
@Test
@MediumTest
@Feature({"AndroidWebView"})
public void testNotCalledOnDomModificationForBlankWebView() throws Throwable {
TestWebServer webServer = TestWebServer.start();
try {
doTestOnPageFinishedNotCalledOnDomMutation(webServer, null);
} finally {
webServer.shutdown();
}
}
@Test
@MediumTest
@Feature({"AndroidWebView"})
public void testNotCalledOnDomModificationAfterNonCommittedLoadFromApi() throws Throwable {
AwActivityTestRule.enableJavaScriptOnUiThread(mAwContents);
TestWebServer webServer = TestWebServer.start();
try {
final String noContentUrl = webServer.setResponseWithNoContentStatus("/nocontent.html");
mActivityTestRule.loadUrlAsync(mAwContents, noContentUrl);
doTestOnPageFinishedNotCalledOnDomMutation(webServer, noContentUrl);
} finally {
webServer.shutdown();
}
}
@Test
@MediumTest
@Feature({"AndroidWebView"})
public void testNotCalledOnDomModificationWithJavascriptUrlAfterNonCommittedLoadFromApi()
throws Throwable {
AwActivityTestRule.enableJavaScriptOnUiThread(mAwContents);
TestWebServer webServer = TestWebServer.start();
try {
final CountDownLatch latch = new CountDownLatch(1);
final String url = webServer.setResponseWithRunnableAction(
"/about.html", CommonResources.ABOUT_HTML, null,
() -> {
try {
Assert.assertTrue(latch.await(WAIT_TIMEOUT_MS,
java.util.concurrent.TimeUnit.MILLISECONDS));
} catch (InterruptedException e) {
Assert.fail("Caught InterruptedException " + e);
}
});
TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
mContentsClient.getOnPageFinishedHelper();
final int onPageFinishedCallCount = onPageFinishedHelper.getCallCount();
mActivityTestRule.loadUrlAsync(mAwContents, url);
mActivityTestRule.loadUrlAsync(mAwContents,
"javascript:(function(){document.body.innerHTML='Hello,%20World!';})()");
mActivityTestRule.stopLoading(mAwContents);
// We now have 3 possible outcomes:
// - the good one -- onPageFinished only fires for the first load;
// - two bad ones:
// - onPageFinished fires for the dom mutation, then for the first load. (1)
// - onPageFinished fires for the first load, then for the dom mutation; (2)
// We verify that (1) doesn't happen with the code below. Then we load a sync page,
// and make sure that we are getting onPageFinished for the sync page, not due
// to the dom mutation, thus verifying that (2) doesn't happen as well.
onPageFinishedHelper.waitForCallback(onPageFinishedCallCount);
Assert.assertEquals(url, onPageFinishedHelper.getUrl());
Assert.assertEquals(onPageFinishedCallCount + 1, onPageFinishedHelper.getCallCount());
latch.countDown(); // Release the server.
final String syncUrl = webServer.setResponse("/sync.html", "", null);
mActivityTestRule.loadUrlAsync(mAwContents, syncUrl);
onPageFinishedHelper.waitForCallback(onPageFinishedCallCount + 1);
Assert.assertEquals(syncUrl, onPageFinishedHelper.getUrl());
Assert.assertEquals(onPageFinishedCallCount + 2, onPageFinishedHelper.getCallCount());
} finally {
webServer.shutdown();
}
}
@Test
@MediumTest
@Feature({"AndroidWebView"})
public void testNotCalledOnDomModificationAfterLoadUrl() throws Throwable {
TestWebServer webServer = TestWebServer.start();
try {
final String testUrl =
webServer.setResponse("/test.html", CommonResources.ABOUT_HTML, null);
mActivityTestRule.loadUrlSync(
mAwContents, mContentsClient.getOnPageFinishedHelper(), testUrl);
doTestOnPageFinishedNotCalledOnDomMutation(webServer, null);
} finally {
webServer.shutdown();
}
}
@Test
@MediumTest
@Feature({"AndroidWebView"})
public void testNotCalledOnDomModificationAfterLoadData() throws Throwable {
TestWebServer webServer = TestWebServer.start();
try {
mActivityTestRule.loadDataSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
CommonResources.ABOUT_HTML, "text/html", false);
doTestOnPageFinishedNotCalledOnDomMutation(webServer, null);
} finally {
webServer.shutdown();
}
}
private void doTestOnPageFinishedNotCalledOnDomMutation(TestWebServer webServer, String syncUrl)
throws Throwable {
AwActivityTestRule.enableJavaScriptOnUiThread(mAwContents);
TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
mContentsClient.getOnPageFinishedHelper();
final int onPageFinishedCallCount = onPageFinishedHelper.getCallCount();
// Mutate DOM.
mActivityTestRule.executeJavaScriptAndWaitForResult(
mAwContents, mContentsClient, "document.body.innerHTML='Hello, World!'");
// Rather than wait a fixed time to see that an onPageFinished callback isn't issued
// we load another valid page. Since callbacks arrive sequentially if the next callback
// we get is for the synchronizationUrl we know that DOM mutation did not schedule
// a callback for the iframe.
if (syncUrl == null) {
syncUrl = webServer.setResponse("/sync.html", "", null);
mActivityTestRule.loadUrlAsync(mAwContents, syncUrl);
}
onPageFinishedHelper.waitForCallback(onPageFinishedCallCount);
Assert.assertEquals(syncUrl, onPageFinishedHelper.getUrl());
Assert.assertEquals(onPageFinishedCallCount + 1, onPageFinishedHelper.getCallCount());
}
@Test
@MediumTest
@Feature({"AndroidWebView"})
public void testCalledAfter204Reply() throws Throwable {
TestWebServer webServer = TestWebServer.start();
try {
final String url = webServer.setResponseWithNoContentStatus("/page.html");
TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
mContentsClient.getOnPageFinishedHelper();
int currentCallCount = onPageFinishedHelper.getCallCount();
mActivityTestRule.loadUrlAsync(mAwContents, url);
onPageFinishedHelper.waitForCallback(currentCallCount);
Assert.assertEquals(url, onPageFinishedHelper.getUrl());
} finally {
webServer.shutdown();
}
}
/**
* Ensure onPageFinished is called for an empty response (if the response status isn't
* HttpStatus.SC_NO_CONTENT).
*/
@Test
@MediumTest
@Feature({"AndroidWebView"})
public void testCalledOnEmptyResponse() throws Throwable {
TestWebServer webServer = TestWebServer.start();
try {
final String url = webServer.setEmptyResponse("/page.html");
TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
mContentsClient.getOnPageFinishedHelper();
int currentCallCount = onPageFinishedHelper.getCallCount();
mActivityTestRule.loadUrlAsync(mAwContents, url);
onPageFinishedHelper.waitForCallback(currentCallCount);
Assert.assertEquals(url, onPageFinishedHelper.getUrl());
} finally {
webServer.shutdown();
}
}
/**
* Ensure onPageFinished is called when a provisional load is cancelled.
*/
@Test
@MediumTest
@Feature({"AndroidWebView"})
public void testCalledOnCancelingProvisionalLoad() throws Throwable {
TestWebServer webServer = TestWebServer.start();
final CountDownLatch firstUrlLatch = new CountDownLatch(1);
try {
final String url = webServer.setResponseWithRunnableAction(
"/slow_page.html", "", null /* headers */, () -> {
try {
// Delay the server response so that we guarantee stopLoading() comes
// before the server response.
Assert.assertTrue(firstUrlLatch.await(
WAIT_TIMEOUT_MS, java.util.concurrent.TimeUnit.MILLISECONDS));
} catch (InterruptedException e) {
Assert.fail("Caught InterruptedException " + e);
}
});
TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
mContentsClient.getOnPageFinishedHelper();
int initialCallCount = onPageFinishedHelper.getCallCount();
mActivityTestRule.loadUrlAsync(mAwContents, url);
// Cancel before getting a response from the server.
mActivityTestRule.stopLoading(mAwContents);
onPageFinishedHelper.waitForCallback(initialCallCount);
Assert.assertEquals(url, onPageFinishedHelper.getUrl());
firstUrlLatch.countDown();
// Load another page to ensure onPageFinished isn't called several times.
final String syncUrl = webServer.setResponse("/sync.html", "", null /* headers */);
mActivityTestRule.loadUrlSync(mAwContents, onPageFinishedHelper, syncUrl);
Assert.assertEquals(syncUrl, onPageFinishedHelper.getUrl());
final int finalCallCount = onPageFinishedHelper.getCallCount();
Assert.assertEquals(
"onPageFinished should be called twice", initialCallCount + 2, finalCallCount);
} finally {
webServer.shutdown();
}
}
/**
* Ensure onPageFinished is called when a committed load is cancelled.
*/
@Test
@MediumTest
@Feature({"AndroidWebView"})
public void testCalledOnCancelingCommittedLoad() throws Throwable {
TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
mContentsClient.getOnPageFinishedHelper();
TestWebServer webServer = TestWebServer.start();
final CountDownLatch serverImageUrlLatch = new CountDownLatch(1);
final CountDownLatch testDoneLatch = new CountDownLatch(1);
try {
final String stallingImageUrl = webServer.setResponseWithRunnableAction(
"/stallingImage.html", "", null /* headers */, () -> {
serverImageUrlLatch.countDown();
try {
Assert.assertTrue(testDoneLatch.await(WAIT_TIMEOUT_MS,
java.util.concurrent.TimeUnit.MILLISECONDS));
} catch (InterruptedException e) {
Assert.fail("Caught InterruptedException " + e);
}
});
final String mainPageHtml =
CommonResources.makeHtmlPageFrom("", "<img src=" + stallingImageUrl + ">");
final String mainPageUrl = webServer.setResponse("/mainPage.html", mainPageHtml, null);
Assert.assertEquals(0, onPageFinishedHelper.getCallCount());
mActivityTestRule.loadUrlAsync(mAwContents, mainPageUrl);
Assert.assertTrue(serverImageUrlLatch.await(
WAIT_TIMEOUT_MS, java.util.concurrent.TimeUnit.MILLISECONDS));
Assert.assertEquals(0, onPageFinishedHelper.getCallCount());
// Our load isn't done since we haven't loaded the image - now cancel the load.
mActivityTestRule.stopLoading(mAwContents);
onPageFinishedHelper.waitForCallback(0);
Assert.assertEquals(1, onPageFinishedHelper.getCallCount());
} finally {
testDoneLatch.countDown();
webServer.shutdown();
}
}
}