[go: nahoru, domu]

blob: 42fc653f5d85f10af4b72fcf9612c62b52ca8dcf [file] [log] [blame]
// 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.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThan;
import static org.chromium.android_webview.test.AwActivityTestRule.WAIT_TIMEOUT_MS;
import static org.chromium.android_webview.test.OnlyRunIn.ProcessMode.MULTI_PROCESS;
import static org.chromium.android_webview.test.OnlyRunIn.ProcessMode.SINGLE_PROCESS;
import android.annotation.SuppressLint;
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Pair;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.webkit.JavascriptInterface;
import androidx.annotation.NonNull;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.SettableFuture;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
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.AwRenderProcess;
import org.chromium.android_webview.AwSettings;
import org.chromium.android_webview.common.AwFeatures;
import org.chromium.android_webview.common.PlatformServiceBridge;
import org.chromium.android_webview.renderer_priority.RendererPriority;
import org.chromium.android_webview.test.TestAwContentsClient.OnDownloadStartHelper;
import org.chromium.android_webview.test.util.CommonResources;
import org.chromium.android_webview.test.util.GraphicsTestUtils;
import org.chromium.base.BaseFeatures;
import org.chromium.base.ContextUtils;
import org.chromium.base.FakeTimeTestRule;
import org.chromium.base.Log;
import org.chromium.base.TimeUtils;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.DisabledTest;
import org.chromium.base.test.util.DoNotBatch;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.Features;
import org.chromium.base.test.util.HistogramWatcher;
import org.chromium.base.test.util.MinAndroidSdkLevel;
import org.chromium.content_public.browser.test.util.RenderProcessHostUtils;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.content_public.browser.test.util.TouchCommon;
import org.chromium.content_public.common.ContentSwitches;
import org.chromium.content_public.common.ContentUrlConstants;
import org.chromium.net.test.EmbeddedTestServer;
import org.chromium.net.test.util.TestWebServer;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
import java.util.function.Predicate;
/** AwContents tests. */
@RunWith(Parameterized.class)
@UseParametersRunnerFactory(AwJUnit4ClassRunnerWithParameters.Factory.class)
@DoNotBatch(reason = "Tests that need browser start are incompatible with @Batch")
public class AwContentsTest extends AwParameterizedTest {
private static final String TAG = "AwContentsTest";
@Rule public AwActivityTestRule mActivityTestRule;
public AwContentsTest(AwSettingsMutation param) {
mActivityTestRule =
new AwActivityTestRule(param.getMutation()) {
// Allow specific tests to use vulkan.
@Override
public boolean needsBrowserProcessStarted() {
return false;
}
};
}
@Rule public FakeTimeTestRule mFakeTimeTestRule = new FakeTimeTestRule();
@Rule public TestRule mProcessor = new Features.InstrumentationProcessor();
private TestAwContentsClient mContentsClient = new TestAwContentsClient();
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testCreateDestroy() throws Throwable {
mActivityTestRule.startBrowserProcess();
// NOTE this test runs on UI thread, so we cannot call any async methods.
mActivityTestRule.runOnUiThread(
() ->
mActivityTestRule
.createAwTestContainerView(mContentsClient)
.getAwContents()
.destroy());
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testCreateLoadPageDestroy() throws Throwable {
mActivityTestRule.startBrowserProcess();
AwTestContainerView awTestContainerView =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
mActivityTestRule.loadDataSync(
awTestContainerView.getAwContents(),
mContentsClient.getOnPageFinishedHelper(),
CommonResources.ABOUT_HTML,
"text/html",
false);
mActivityTestRule.destroyAwContentsOnMainSync(awTestContainerView.getAwContents());
// It should be safe to call destroy multiple times.
mActivityTestRule.destroyAwContentsOnMainSync(awTestContainerView.getAwContents());
}
@Test
@LargeTest
@Feature({"AndroidWebView"})
public void testCreateLoadDestroyManyTimes() throws Throwable {
mActivityTestRule.startBrowserProcess();
for (int i = 0; i < 10; ++i) {
AwTestContainerView testView =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
AwContents awContents = testView.getAwContents();
mActivityTestRule.loadUrlSync(
awContents,
mContentsClient.getOnPageFinishedHelper(),
ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL);
mActivityTestRule.destroyAwContentsOnMainSync(awContents);
}
}
@Test
@LargeTest
@Feature({"AndroidWebView"})
public void testCreateLoadDestroyManyAtOnce() throws Throwable {
mActivityTestRule.startBrowserProcess();
AwTestContainerView[] views = new AwTestContainerView[10];
for (int i = 0; i < views.length; ++i) {
views[i] = mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
mActivityTestRule.loadUrlSync(
views[i].getAwContents(),
mContentsClient.getOnPageFinishedHelper(),
ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL);
}
for (int i = 0; i < views.length; ++i) {
mActivityTestRule.destroyAwContentsOnMainSync(views[i].getAwContents());
views[i] = null;
}
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testWebViewApisFailGracefullyAfterDestruction() throws Throwable {
mActivityTestRule.startBrowserProcess();
mActivityTestRule.runOnUiThread(
() -> {
AwContents awContents =
mActivityTestRule
.createAwTestContainerView(mContentsClient)
.getAwContents();
awContents.destroy();
// The documentation for WebView#destroy() reads "This method should be called
// after this WebView has been removed from the view system. No other methods
// may be called on this WebView after destroy".
// However, some apps do not respect that restriction so we need to ensure that
// we fail gracefully and do not crash when APIs are invoked after destruction.
// Due to the large number of APIs we only test a representative selection here.
awContents.clearHistory();
Assert.assertNull(awContents.getOriginalUrl());
Assert.assertNull(awContents.getNavigationHistory());
awContents.loadUrl("http://www.google.com");
awContents.findAllAsync("search");
Assert.assertNull(awContents.getUrl());
Assert.assertFalse(awContents.canGoBack());
awContents.disableJavascriptInterfacesInspection();
awContents.invokeZoomPicker();
awContents.onResume();
awContents.stopLoading();
awContents.onWindowVisibilityChanged(View.VISIBLE);
awContents.requestFocus();
awContents.isMultiTouchZoomSupported();
awContents.setOverScrollMode(View.OVER_SCROLL_NEVER);
awContents.pauseTimers();
awContents.onContainerViewScrollChanged(200, 200, 100, 100);
awContents.computeScroll();
awContents.onMeasure(100, 100);
awContents.onDraw(new Canvas());
awContents.getMostRecentProgress();
Assert.assertEquals(0, awContents.computeHorizontalScrollOffset());
Assert.assertEquals(0, awContents.getContentWidthCss());
awContents.onKeyUp(
KeyEvent.KEYCODE_BACK,
new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU));
});
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testUseAwSettingsAfterDestroy() throws Throwable {
mActivityTestRule.startBrowserProcess();
AwTestContainerView awTestContainerView =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
AwSettings awSettings =
mActivityTestRule.getAwSettingsOnUiThread(awTestContainerView.getAwContents());
mActivityTestRule.loadDataSync(
awTestContainerView.getAwContents(),
mContentsClient.getOnPageFinishedHelper(),
CommonResources.ABOUT_HTML,
"text/html",
false);
mActivityTestRule.destroyAwContentsOnMainSync(awTestContainerView.getAwContents());
// AwSettings should still be usable even after native side is destroyed.
String newFontFamily = "serif";
awSettings.setStandardFontFamily(newFontFamily);
Assert.assertEquals(newFontFamily, awSettings.getStandardFontFamily());
boolean newBlockNetworkLoads = !awSettings.getBlockNetworkLoads();
awSettings.setBlockNetworkLoads(newBlockNetworkLoads);
Assert.assertEquals(newBlockNetworkLoads, awSettings.getBlockNetworkLoads());
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testGoBackGoForwardWithoutSessionHistory() throws Throwable {
mActivityTestRule.startBrowserProcess();
mActivityTestRule.runOnUiThread(
() -> {
AwContents awContents =
mActivityTestRule
.createAwTestContainerView(mContentsClient)
.getAwContents();
Assert.assertFalse(awContents.canGoBack());
Assert.assertFalse(awContents.canGoForward());
// If no back/forward entries exist, then calling these should do nothing and
// not crash or fail asserts.
awContents.goBack();
awContents.goForward();
});
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testBackgroundColorInDarkMode() throws Throwable {
mActivityTestRule.startBrowserProcess();
mActivityTestRule.runOnUiThread(
() -> {
AwContents awContents =
mActivityTestRule
.createAwTestContainerView(mContentsClient)
.getAwContents();
AwSettings awSettings = awContents.getSettings();
Assert.assertEquals(
awContents.getEffectiveBackgroundColorForTesting(), Color.WHITE);
awSettings.setForceDarkMode(AwSettings.FORCE_DARK_ON);
Assert.assertTrue(awSettings.isForceDarkApplied());
Assert.assertEquals(
awContents.getEffectiveBackgroundColorForTesting(), Color.BLACK);
awContents.setBackgroundColor(Color.RED);
Assert.assertEquals(
awContents.getEffectiveBackgroundColorForTesting(), Color.RED);
awContents.destroy();
Assert.assertEquals(
awContents.getEffectiveBackgroundColorForTesting(), Color.RED);
});
}
private int callDocumentHasImagesSync(final AwContents awContents)
throws Throwable, InterruptedException {
// Set up a container to hold the result object and a semaphore to
// make the test wait for the result.
final AtomicInteger val = new AtomicInteger();
final Semaphore s = new Semaphore(0);
final Message msg =
Message.obtain(
new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
val.set(msg.arg1);
s.release();
}
});
InstrumentationRegistry.getInstrumentation()
.runOnMainSync(() -> awContents.documentHasImages(msg));
Assert.assertTrue(
s.tryAcquire(AwActivityTestRule.SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
int result = val.get();
return result;
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testDocumentHasImages() throws Throwable {
mActivityTestRule.startBrowserProcess();
AwTestContainerView testView =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
AwContents awContents = testView.getAwContents();
final CallbackHelper loadHelper = mContentsClient.getOnPageFinishedHelper();
final String mime = "text/html";
final String emptyDoc = "<head/><body/>";
final String imageDoc = "<head/><body><img/><img/></body>";
// Make sure a document that does not have images returns 0
mActivityTestRule.loadDataSync(awContents, loadHelper, emptyDoc, mime, false);
int result = callDocumentHasImagesSync(awContents);
Assert.assertEquals(0, result);
// Make sure a document that does have images returns 1
mActivityTestRule.loadDataSync(awContents, loadHelper, imageDoc, mime, false);
result = callDocumentHasImagesSync(awContents);
Assert.assertEquals(1, result);
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
@SkipMutations(reason = "This test depends on AwSettings.setCacheMode()")
public void testClearCacheMemoryAndDisk() throws Throwable {
mActivityTestRule.startBrowserProcess();
final AwTestContainerView testContainer =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
final AwContents awContents = testContainer.getAwContents();
TestWebServer webServer = TestWebServer.start();
try {
final String pagePath = "/clear_cache_test.html";
List<Pair<String, String>> headers = new ArrayList<Pair<String, String>>();
// Set Cache-Control headers to cache this request. One century should be long enough.
headers.add(Pair.create("Cache-Control", "max-age=3153600000"));
headers.add(Pair.create("Last-Modified", "Wed, 3 Oct 2012 00:00:00 GMT"));
final String pageUrl =
webServer.setResponse(pagePath, "<html><body>foo</body></html>", headers);
// First load to populate cache.
mActivityTestRule.clearCacheOnUiThread(awContents, true);
mActivityTestRule.loadUrlSync(
awContents, mContentsClient.getOnPageFinishedHelper(), pageUrl);
Assert.assertEquals(1, webServer.getRequestCount(pagePath));
// Load about:blank so next load is not treated as reload by webkit and force
// revalidate with the server.
mActivityTestRule.loadUrlSync(
awContents,
mContentsClient.getOnPageFinishedHelper(),
ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL);
// No clearCache call, so should be loaded from cache.
mActivityTestRule.loadUrlSync(
awContents, mContentsClient.getOnPageFinishedHelper(), pageUrl);
Assert.assertEquals(1, webServer.getRequestCount(pagePath));
// Same as above.
mActivityTestRule.loadUrlSync(
awContents,
mContentsClient.getOnPageFinishedHelper(),
ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL);
// Clear cache, so should hit server again.
mActivityTestRule.clearCacheOnUiThread(awContents, true);
mActivityTestRule.loadUrlSync(
awContents, mContentsClient.getOnPageFinishedHelper(), pageUrl);
Assert.assertEquals(2, webServer.getRequestCount(pagePath));
} finally {
webServer.shutdown();
}
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testClearCacheInQuickSuccession() {
mActivityTestRule.startBrowserProcess();
final AwTestContainerView testContainer =
mActivityTestRule.createAwTestContainerViewOnMainSync(new TestAwContentsClient());
final AwContents awContents = testContainer.getAwContents();
InstrumentationRegistry.getInstrumentation()
.runOnMainSync(
() -> {
for (int i = 0; i < 10; ++i) {
awContents.clearCache(true);
}
});
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testGetFavicon() throws Throwable {
mActivityTestRule.startBrowserProcess();
AwContents.setShouldDownloadFavicons();
final AwTestContainerView testView =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
final AwContents awContents = testView.getAwContents();
TestWebServer webServer = TestWebServer.start();
try {
final String faviconUrl =
webServer.setResponseBase64(
"/" + CommonResources.FAVICON_FILENAME,
CommonResources.FAVICON_DATA_BASE64,
CommonResources.getImagePngHeaders(false));
final String pageUrl =
webServer.setResponse(
"/favicon.html", CommonResources.FAVICON_STATIC_HTML, null);
// The getFavicon will return the right icon a certain time after
// the page load completes which makes it slightly hard to test.
final Bitmap defaultFavicon = awContents.getFavicon();
mActivityTestRule.getAwSettingsOnUiThread(awContents).setImagesEnabled(true);
mActivityTestRule.loadUrlSync(
awContents, mContentsClient.getOnPageFinishedHelper(), pageUrl);
mActivityTestRule.pollUiThread(
() ->
awContents.getFavicon() != null
&& !awContents.getFavicon().sameAs(defaultFavicon));
final Object originalFaviconSource = (new URL(faviconUrl)).getContent();
final Bitmap originalFavicon =
BitmapFactory.decodeStream((InputStream) originalFaviconSource);
Assert.assertNotNull(originalFavicon);
Assert.assertTrue(awContents.getFavicon().sameAs(originalFavicon));
} finally {
webServer.shutdown();
}
}
@Test
@Feature({"AndroidWebView", "Downloads"})
@SmallTest
@SkipMutations(reason = "This test depends on AwSettings.setUserAgentString()")
public void testDownload() throws Throwable {
downloadAndCheck(null);
}
@Test
@Feature({"AndroidWebView", "Downloads"})
@SmallTest
public void testDownloadWithCustomUserAgent() throws Throwable {
downloadAndCheck("Custom User Agent");
}
private void downloadAndCheck(String customUserAgent) throws Throwable {
mActivityTestRule.startBrowserProcess();
AwTestContainerView testView =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
AwContents awContents = testView.getAwContents();
if (customUserAgent != null) {
AwSettings awSettings = mActivityTestRule.getAwSettingsOnUiThread(awContents);
awSettings.setUserAgentString(customUserAgent);
}
final String data = "download data";
final String contentDisposition = "attachment;filename=\"download.txt\"";
final String mimeType = "text/plain";
List<Pair<String, String>> downloadHeaders = new ArrayList<Pair<String, String>>();
downloadHeaders.add(Pair.create("Content-Disposition", contentDisposition));
downloadHeaders.add(Pair.create("Content-Type", mimeType));
downloadHeaders.add(Pair.create("Content-Length", Integer.toString(data.length())));
TestWebServer webServer = TestWebServer.start();
try {
final String pageUrl = webServer.setResponse("/download.txt", data, downloadHeaders);
final OnDownloadStartHelper downloadStartHelper =
mContentsClient.getOnDownloadStartHelper();
final int callCount = downloadStartHelper.getCallCount();
mActivityTestRule.loadUrlAsync(awContents, pageUrl);
downloadStartHelper.waitForCallback(callCount);
Assert.assertEquals(pageUrl, downloadStartHelper.getUrl());
Assert.assertEquals(contentDisposition, downloadStartHelper.getContentDisposition());
Assert.assertEquals(mimeType, downloadStartHelper.getMimeType());
Assert.assertEquals(data.length(), downloadStartHelper.getContentLength());
Assert.assertFalse(downloadStartHelper.getUserAgent().isEmpty());
if (customUserAgent != null) {
Assert.assertEquals(customUserAgent, downloadStartHelper.getUserAgent());
} else {
Assert.assertEquals(
downloadStartHelper.getUserAgent(), AwSettings.getDefaultUserAgent());
}
} finally {
webServer.shutdown();
}
}
@Test
@Feature({"AndroidWebView", "setNetworkAvailable"})
@SmallTest
public void testSetNetworkAvailable() throws Throwable {
mActivityTestRule.startBrowserProcess();
AwTestContainerView testView =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
AwContents awContents = testView.getAwContents();
String script = "navigator.onLine";
AwActivityTestRule.enableJavaScriptOnUiThread(awContents);
mActivityTestRule.loadUrlSync(
awContents,
mContentsClient.getOnPageFinishedHelper(),
ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL);
// Default to "online".
Assert.assertEquals(
"true",
mActivityTestRule.executeJavaScriptAndWaitForResult(
awContents, mContentsClient, script));
// Forcing "offline".
AwActivityTestRule.setNetworkAvailableOnUiThread(awContents, false);
Assert.assertEquals(
"false",
mActivityTestRule.executeJavaScriptAndWaitForResult(
awContents, mContentsClient, script));
// Forcing "online".
AwActivityTestRule.setNetworkAvailableOnUiThread(awContents, true);
Assert.assertEquals(
"true",
mActivityTestRule.executeJavaScriptAndWaitForResult(
awContents, mContentsClient, script));
}
static class JavaScriptObject {
private CallbackHelper mCallbackHelper;
public JavaScriptObject(CallbackHelper callbackHelper) {
mCallbackHelper = callbackHelper;
}
@JavascriptInterface
public void run() {
mCallbackHelper.notifyCalled();
}
}
@Test
@Feature({"AndroidWebView", "Android-JavaBridge"})
@SmallTest
public void testJavaBridge() throws Throwable {
mActivityTestRule.startBrowserProcess();
final AwTestContainerView testView =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
final CallbackHelper callback = new CallbackHelper();
AwContents awContents = testView.getAwContents();
AwActivityTestRule.enableJavaScriptOnUiThread(awContents);
AwActivityTestRule.addJavascriptInterfaceOnUiThread(
awContents, new JavaScriptObject(callback), "bridge");
mActivityTestRule.executeJavaScriptAndWaitForResult(
awContents, mContentsClient, "window.bridge.run();");
callback.waitForCallback(0, 1, WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
@Test
@Feature({"AndroidWebView"})
@SmallTest
public void testEscapingOfErrorPage() throws Throwable {
mActivityTestRule.startBrowserProcess();
AwTestContainerView testView =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
AwContents awContents = testView.getAwContents();
String script = "window.failed == true";
AwActivityTestRule.enableJavaScriptOnUiThread(awContents);
CallbackHelper onPageFinishedHelper = mContentsClient.getOnPageFinishedHelper();
int currentCallCount = onPageFinishedHelper.getCallCount();
mActivityTestRule.loadUrlAsync(
awContents,
"file:///file-that-does-not-exist#<script>window.failed = true;</script>");
onPageFinishedHelper.waitForCallback(
currentCallCount, 1, WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
Assert.assertEquals(
"false",
mActivityTestRule.executeJavaScriptAndWaitForResult(
awContents, mContentsClient, script));
}
@Test
@Feature({"AndroidWebView"})
@SmallTest
public void testCanInjectHeaders() throws Throwable {
mActivityTestRule.startBrowserProcess();
final AwTestContainerView testContainer =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
final AwContents awContents = testContainer.getAwContents();
AwActivityTestRule.enableJavaScriptOnUiThread(awContents);
EmbeddedTestServer testServer =
EmbeddedTestServer.createAndStartServer(
InstrumentationRegistry.getInstrumentation().getContext());
String url = testServer.getURL("/echoheader?X-foo");
final Map<String, String> extraHeaders = new HashMap<String, String>();
extraHeaders.put("X-foo", "bar");
mActivityTestRule.loadUrlSync(
awContents, mContentsClient.getOnPageFinishedHelper(), url, extraHeaders);
String xfoo =
mActivityTestRule.getJavaScriptResultBodyTextContent(awContents, mContentsClient);
Assert.assertEquals("bar", xfoo);
url = testServer.getURL("/echoheader?Referer");
mActivityTestRule.loadUrlSync(
awContents,
mContentsClient.getOnPageFinishedHelper(),
url,
ImmutableMap.of("Referer", "http://www.example.com/"));
String referer =
mActivityTestRule.getJavaScriptResultBodyTextContent(awContents, mContentsClient);
Assert.assertEquals("http://www.example.com/", referer);
}
// This is a meta test that we don't accidentally turn off hardware
// acceleration in instrumentation tests without notice. Do not add the
// @DisableHardwareAcceleration annotation for this test.
@Test
@Feature({"AndroidWebView"})
@SmallTest
public void testHardwareModeWorks() {
mActivityTestRule.startBrowserProcess();
AwTestContainerView testContainer =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
Assert.assertTrue(testContainer.isHardwareAccelerated());
Assert.assertTrue(testContainer.isBackedByHardwareView());
}
@Test
@Feature({"AndroidWebView"})
@SmallTest
public void testBasicCookieFunctionality() throws Throwable {
mActivityTestRule.startBrowserProcess();
AwTestContainerView testView =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
AwContents awContents = testView.getAwContents();
TestWebServer webServer = TestWebServer.start();
try {
List<Pair<String, String>> responseHeaders = CommonResources.getTextHtmlHeaders(true);
final String cookie = "key=value";
responseHeaders.add(Pair.create("Set-Cookie", cookie));
final String url =
webServer.setResponse(
"/" + CommonResources.ABOUT_FILENAME,
CommonResources.ABOUT_HTML,
responseHeaders);
AwActivityTestRule.enableJavaScriptOnUiThread(awContents);
mActivityTestRule.loadUrlSync(
awContents, mContentsClient.getOnPageFinishedHelper(), url);
final String script = "document.cookie";
Assert.assertEquals(
"\"key=value\"",
mActivityTestRule.executeJavaScriptAndWaitForResult(
awContents, mContentsClient, script));
} finally {
webServer.shutdown();
}
}
/** Verifies that Web Notifications and the Push API are not exposed in WebView. */
@Test
@Feature({"AndroidWebView"})
@SmallTest
public void testPushAndNotificationsDisabled() throws Throwable {
mActivityTestRule.startBrowserProcess();
AwTestContainerView testView =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
AwContents awContents = testView.getAwContents();
String script = "window.Notification || window.PushManager";
AwActivityTestRule.enableJavaScriptOnUiThread(awContents);
mActivityTestRule.loadUrlSync(
awContents,
mContentsClient.getOnPageFinishedHelper(),
ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL);
Assert.assertEquals(
"null",
mActivityTestRule.executeJavaScriptAndWaitForResult(
awContents, mContentsClient, script));
}
private @RendererPriority int getRendererPriorityOnUiThread(final AwContents awContents)
throws Exception {
return TestThreadUtils.runOnUiThreadBlocking(
() -> awContents.getEffectivePriorityForTesting());
}
private void setRendererPriorityOnUiThread(
final AwContents awContents,
final @RendererPriority int priority,
final boolean waivedWhenNotVisible)
throws Throwable {
mActivityTestRule.runOnUiThread(
() -> awContents.setRendererPriorityPolicy(priority, waivedWhenNotVisible));
}
@Test
@Feature({"AndroidWebView"})
@SmallTest
@OnlyRunIn(MULTI_PROCESS)
@CommandLineFlags.Add(ContentSwitches.RENDER_PROCESS_LIMIT + "=1")
public void testForegroundPriorityOneProcess() throws Throwable {
mActivityTestRule.startBrowserProcess();
final AwTestContainerView view1 =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
final AwContents contents1 = view1.getAwContents();
final AwTestContainerView view2 =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
final AwContents contents2 = view2.getAwContents();
mActivityTestRule.loadUrlSync(
contents1,
mContentsClient.getOnPageFinishedHelper(),
ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL);
mActivityTestRule.loadUrlSync(
contents2,
mContentsClient.getOnPageFinishedHelper(),
ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL);
// Process should start out high.
Assert.assertEquals(RendererPriority.HIGH, getRendererPriorityOnUiThread(contents1));
Assert.assertEquals(RendererPriority.HIGH, getRendererPriorityOnUiThread(contents2));
// Set one to low. Process should take max priority of contents, so still high.
setRendererPriorityOnUiThread(contents1, RendererPriority.LOW, false);
Assert.assertEquals(RendererPriority.HIGH, getRendererPriorityOnUiThread(contents1));
Assert.assertEquals(RendererPriority.HIGH, getRendererPriorityOnUiThread(contents2));
// Set both to low and check.
setRendererPriorityOnUiThread(contents2, RendererPriority.LOW, false);
Assert.assertEquals(RendererPriority.LOW, getRendererPriorityOnUiThread(contents1));
Assert.assertEquals(RendererPriority.LOW, getRendererPriorityOnUiThread(contents2));
// Set both to waive and check.
setRendererPriorityOnUiThread(contents1, RendererPriority.WAIVED, false);
setRendererPriorityOnUiThread(contents2, RendererPriority.WAIVED, false);
Assert.assertEquals(RendererPriority.WAIVED, getRendererPriorityOnUiThread(contents1));
Assert.assertEquals(RendererPriority.WAIVED, getRendererPriorityOnUiThread(contents2));
// Set one to high and check.
setRendererPriorityOnUiThread(contents1, RendererPriority.HIGH, false);
Assert.assertEquals(RendererPriority.HIGH, getRendererPriorityOnUiThread(contents1));
Assert.assertEquals(RendererPriority.HIGH, getRendererPriorityOnUiThread(contents2));
// Destroy contents with high priority, and process should fall back to low.
// Destroy posts on UI, but getRendererPriorityOnUiThread posts after, so there should
// be no flakiness and no need for polling.
mActivityTestRule.destroyAwContentsOnMainSync(contents1);
Assert.assertEquals(RendererPriority.WAIVED, getRendererPriorityOnUiThread(contents2));
}
@Test
@Feature({"AndroidWebView"})
@SmallTest
@OnlyRunIn(MULTI_PROCESS)
@CommandLineFlags.Add(ContentSwitches.RENDER_PROCESS_LIMIT + "=2")
public void testForegroundPriorityTwoProcesses() throws Throwable {
mActivityTestRule.startBrowserProcess();
final AwTestContainerView view1 =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
final AwContents contents1 = view1.getAwContents();
final AwTestContainerView view2 =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
final AwContents contents2 = view2.getAwContents();
mActivityTestRule.loadUrlSync(
contents1,
mContentsClient.getOnPageFinishedHelper(),
ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL);
mActivityTestRule.loadUrlSync(
contents2,
mContentsClient.getOnPageFinishedHelper(),
ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL);
// Process should start out high.
Assert.assertEquals(RendererPriority.HIGH, getRendererPriorityOnUiThread(contents1));
Assert.assertEquals(RendererPriority.HIGH, getRendererPriorityOnUiThread(contents2));
// Set one to low. Other should not be affected.
setRendererPriorityOnUiThread(contents1, RendererPriority.LOW, false);
Assert.assertEquals(RendererPriority.LOW, getRendererPriorityOnUiThread(contents1));
Assert.assertEquals(RendererPriority.HIGH, getRendererPriorityOnUiThread(contents2));
}
@Test
@Feature({"AndroidWebView"})
@SmallTest
@OnlyRunIn(MULTI_PROCESS)
public void testBackgroundPriority() throws Throwable {
mActivityTestRule.startBrowserProcess();
final AwContents awContents =
mActivityTestRule
.createAwTestContainerViewOnMainSync(mContentsClient)
.getAwContents();
Assert.assertEquals(RendererPriority.HIGH, getRendererPriorityOnUiThread(awContents));
mActivityTestRule.runOnUiThread(() -> awContents.onPause());
Assert.assertEquals(RendererPriority.HIGH, getRendererPriorityOnUiThread(awContents));
setRendererPriorityOnUiThread(
awContents, RendererPriority.HIGH, /* waivedWhenNotVisible= */ true);
Assert.assertEquals(RendererPriority.WAIVED, getRendererPriorityOnUiThread(awContents));
mActivityTestRule.runOnUiThread(() -> awContents.onResume());
Assert.assertEquals(RendererPriority.HIGH, getRendererPriorityOnUiThread(awContents));
}
@Test
@Feature({"AndroidWebView"})
@SmallTest
@OnlyRunIn(MULTI_PROCESS)
public void testPauseDestroyResume() throws Throwable {
mActivityTestRule.startBrowserProcess();
mActivityTestRule.runOnUiThread(
() -> {
AwContents awContents;
awContents =
mActivityTestRule
.createAwTestContainerView(mContentsClient)
.getAwContents();
awContents.pauseTimers();
awContents.pauseTimers();
awContents.destroy();
awContents =
mActivityTestRule
.createAwTestContainerView(mContentsClient)
.getAwContents();
awContents.resumeTimers();
});
}
private AwRenderProcess getRenderProcessOnUiThread(final AwContents awContents)
throws Exception {
return TestThreadUtils.runOnUiThreadBlocking(() -> awContents.getRenderProcess());
}
@Test
@Feature({"AndroidWebView"})
@SmallTest
@OnlyRunIn(MULTI_PROCESS)
public void testRenderProcessInMultiProcessMode() throws Throwable {
mActivityTestRule.startBrowserProcess();
AwTestContainerView testView =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
final AwContents awContents = testView.getAwContents();
final AwRenderProcess preLoadRenderProcess = getRenderProcessOnUiThread(awContents);
Assert.assertNotNull(preLoadRenderProcess);
mActivityTestRule.loadUrlSync(
awContents,
mContentsClient.getOnPageFinishedHelper(),
ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL);
final AwRenderProcess postLoadRenderProcess = getRenderProcessOnUiThread(awContents);
Assert.assertEquals(preLoadRenderProcess, postLoadRenderProcess);
}
@Test
@Feature({"AndroidWebView"})
@SmallTest
@OnlyRunIn(SINGLE_PROCESS)
public void testNoRenderProcessInSingleProcessMode() throws Throwable {
mActivityTestRule.startBrowserProcess();
AwTestContainerView testView =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
final AwContents awContents = testView.getAwContents();
mActivityTestRule.loadUrlSync(
awContents,
mContentsClient.getOnPageFinishedHelper(),
ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL);
final AwRenderProcess renderProcess = getRenderProcessOnUiThread(awContents);
Assert.assertEquals(renderProcess, null);
}
/**
* Regression test for https://crbug.com/732976. Load a data URL, then immediately after that
* load a javascript URL. The data URL navigation shouldn't be blocked.
*/
@Test
@LargeTest
@Feature({"AndroidWebView"})
public void testJavaScriptUrlAfterLoadData() throws Throwable {
mActivityTestRule.startBrowserProcess();
AwTestContainerView testView =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
final AwContents awContents = testView.getAwContents();
mActivityTestRule.runOnUiThread(
() -> {
// Run javascript navigation immediately, without waiting for the completion of
// data URL.
awContents.loadData("<html>test</html>", "text/html", "utf-8");
awContents.loadUrl("javascript: void(0)");
});
mContentsClient
.getOnPageFinishedHelper()
.waitForCallback(0, 1, WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
Assert.assertEquals("data:text/html,<html>test</html>", awContents.getLastCommittedUrl());
TestAwContentsClient.AddMessageToConsoleHelper consoleHelper =
mContentsClient.getAddMessageToConsoleHelper();
Assert.assertEquals(0, consoleHelper.getMessages().size());
}
/**
* Regression test for https://crbug.com/1226748. Call stopLoading() before any page has been
* loaded, load a page, and then load a JavaScript URL. The JavaScript URL should execute.
*/
@Test
@LargeTest
@Feature({"AndroidWebView"})
public void testJavaScriptUrlAfterStopLoading() throws Throwable {
mActivityTestRule.startBrowserProcess();
AwTestContainerView testView =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
final AwContents awContents = testView.getAwContents();
// It should always be safe to call stopLoading() even if we haven't loaded anything yet.
mActivityTestRule.stopLoading(awContents);
mActivityTestRule.loadUrlSync(
awContents,
mContentsClient.getOnPageFinishedHelper(),
ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL);
AwActivityTestRule.enableJavaScriptOnUiThread(awContents);
mActivityTestRule.loadUrlAsync(awContents, "javascript:location.reload()");
// Wait for the page to reload and trigger another onPageFinished()
mContentsClient
.getOnPageFinishedHelper()
.waitForCallback(0, 2, WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
/**
* Regression test for https://crbug.com/1231883. Call stopLoading() before any page has been
* loaded, load a page, and then call evaluateJavaScript. The JavaScript code should execute.
*/
@Test
@LargeTest
@Feature({"AndroidWebView"})
public void testEvaluateJavaScriptAfterStopLoading() throws Throwable {
mActivityTestRule.startBrowserProcess();
AwTestContainerView testView =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
final AwContents awContents = testView.getAwContents();
// It should always be safe to call stopLoading() even if we haven't loaded anything yet.
mActivityTestRule.stopLoading(awContents);
mActivityTestRule.loadUrlSync(
awContents,
mContentsClient.getOnPageFinishedHelper(),
ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL);
AwActivityTestRule.enableJavaScriptOnUiThread(awContents);
mActivityTestRule.runOnUiThread(
() -> {
// We specifically call AwContents.evaluateJavaScript() rather than the
// AwActivityTestRule helper methods to make sure we're using the same code path
// as production.
awContents.evaluateJavaScript("location.reload()", null);
});
// Wait for the page to reload and trigger another onPageFinished()
mContentsClient
.getOnPageFinishedHelper()
.waitForCallback(0, 2, WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
// Verify the callback actually contains the execution result.
final SettableFuture<String> jsResult = SettableFuture.create();
mActivityTestRule.runOnUiThread(
() -> {
// We specifically call AwContents.evaluateJavaScript() rather than the
// AwActivityTestRule helper methods to make sure we're using the same code path
// as production.
awContents.evaluateJavaScript("1 + 2", jsResult::set);
});
Assert.assertEquals(
"JavaScript expression result should be correct",
"3",
AwActivityTestRule.waitForFuture(jsResult));
}
/**
* Regression test for https://crbug.com/1145717. Load a URL that requires fixing and verify
* that the legacy behavior is preserved (i.e. that the URL is fixed + that no crashes happen in
* the product).
*
* <p>The main test verification is that there are no crashes. In particular, this test tries to
* verify that the `loadUrl` call above won't trigger:
* <li>NOTREACHED and DwoC in content::NavigationRequest's constructor for about: scheme
* navigations that aren't about:blank nor about:srcdoc
* <li>CHECK in content::NavigationRequest::GetOriginForURLLoaderFactory caused by the mismatch
* between the result of this method and the "about:" process lock.
*/
@Test
@LargeTest
@Feature({"AndroidWebView"})
public void testLoadUrlAboutVersion() throws Throwable {
mActivityTestRule.startBrowserProcess();
AwTestContainerView testView =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
final AwContents awContents = testView.getAwContents();
mActivityTestRule.runOnUiThread(
() -> {
// "about:safe-browsing" will be rewritten by
// components.url_formatter.UrlFormatter.fixupUrl into
// "chrome://safe-browsing/".
//
// Note that chrome://safe-browsing/ is one of very few chrome://... URLs that
// work
// in Android WebView. In particular, chrome://version/ wouldn't work.
awContents.loadUrl("about:safe-browsing");
});
mContentsClient
.getOnPageFinishedHelper()
.waitForCallback(0, 1, WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
Assert.assertEquals("chrome://safe-browsing/", awContents.getLastCommittedUrl());
}
private void doHardwareRenderingSmokeTest() throws Throwable {
AwTestContainerView testView =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
doHardwareRenderingSmokeTest(testView);
}
private void doHardwareRenderingSmokeTest(AwTestContainerView testView) throws Throwable {
doHardwareRenderingSmokeTest(testView, 128, 128, 128);
}
private void doHardwareRenderingSmokeTest(AwTestContainerView testView, int r, int g, int b)
throws Throwable {
String html =
String.format(
"<html>"
+ " <body style=\""
+ " padding: 0;"
+ " margin: 0;"
+ " display: grid;"
+ " display: grid;"
+ " grid-template-columns: 50%% 50%%;"
+ " grid-template-rows: 50%% 50%%;\">"
+ " <div style=\"background-color: rgb(255, 0, 0);\"></div>"
+ " <div style=\"background-color: rgb(0, 255, 0);\"></div>"
+ " <div style=\"background-color: rgb(0, 0, 255);\"></div>"
+ " <div style=\"background-color: rgb(%d, %d, %d);\"></div>"
+ " </body>"
+ "</html>",
r, g, b);
mActivityTestRule.loadDataSync(
testView.getAwContents(),
mContentsClient.getOnPageFinishedHelper(),
html,
"text/html",
false);
mActivityTestRule.waitForVisualStateCallback(testView.getAwContents());
int[] expectedQuadrantColors = {
Color.rgb(255, 0, 0), Color.rgb(0, 255, 0), Color.rgb(0, 0, 255), Color.rgb(r, g, b)
};
GraphicsTestUtils.pollForQuadrantColors(testView, expectedQuadrantColors);
}
@Test
@Feature({"AndroidWebView"})
@MediumTest
public void testHardwareRenderingSmokeTest() throws Throwable {
mActivityTestRule.startBrowserProcess();
doHardwareRenderingSmokeTest();
}
@Test
@Feature({"AndroidWebView"})
@MediumTest
@MinAndroidSdkLevel(Build.VERSION_CODES.P)
public void testHardwareRenderingSmokeTestVulkanWhereSupported() throws Throwable {
// Manually curated list.
final String[] supportedModels = {
"Pixel", "Pixel 2", "Pixel 3", "Pixel 4a",
};
if (!Arrays.asList(supportedModels).contains(Build.MODEL)) {
Log.w(TAG, "Skipping vulkan test on unknown device: " + Build.MODEL);
return;
}
mActivityTestRule.startBrowserProcessWithVulkan();
doHardwareRenderingSmokeTest();
}
@Test
@Feature({"AndroidWebView"})
@SmallTest
public void testFixupOctothorpesInLoadDataContent() {
mActivityTestRule.startBrowserProcess();
// If there are no octothorpes the function should have no effect.
final String noOctothorpeString = "<div id='foo1'>This content has no octothorpe</div>";
Assert.assertEquals(
noOctothorpeString,
AwContents.fixupOctothorpesInLoadDataContent(noOctothorpeString));
// One '#' followed by a valid DOM id requires us to duplicate it into a real fragment.
Assert.assertEquals("abc%23A#A", AwContents.fixupOctothorpesInLoadDataContent("abc#A"));
Assert.assertEquals("abc%23a#a", AwContents.fixupOctothorpesInLoadDataContent("abc#a"));
Assert.assertEquals("abc%23Aa#Aa", AwContents.fixupOctothorpesInLoadDataContent("abc#Aa"));
Assert.assertEquals("abc%23aA#aA", AwContents.fixupOctothorpesInLoadDataContent("abc#aA"));
Assert.assertEquals(
"abc%23a1-_:.#a1-_:.", AwContents.fixupOctothorpesInLoadDataContent("abc#a1-_:."));
// One '#' followed by an invalid DOM id just means we encode the '#'.
Assert.assertEquals("abc%231", AwContents.fixupOctothorpesInLoadDataContent("abc#1"));
Assert.assertEquals("abc%231a", AwContents.fixupOctothorpesInLoadDataContent("abc#1a"));
Assert.assertEquals(
"abc%23not valid", AwContents.fixupOctothorpesInLoadDataContent("abc#not valid"));
Assert.assertEquals("abc%23a@", AwContents.fixupOctothorpesInLoadDataContent("abc#a@"));
// Multiple '#', whether or not they have a valid DOM id afterwards, just means we encode
// the '#'.
Assert.assertEquals("abc%23%23a", AwContents.fixupOctothorpesInLoadDataContent("abc##a"));
Assert.assertEquals("abc%23a%23b", AwContents.fixupOctothorpesInLoadDataContent("abc#a#b"));
}
@Test
@Feature({"AndroidWebView"})
@SmallTest
public void testLoadDataOctothorpeHandling() throws Throwable {
mActivityTestRule.startBrowserProcess();
AwTestContainerView testView =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
final AwContents awContents = testView.getAwContents();
// Before Android Q, the loadData API is expected to handle the encoding for users.
boolean encodeOctothorpes =
ContextUtils.getApplicationContext().getApplicationInfo().targetSdkVersion
< Build.VERSION_CODES.Q;
// A URL with no '#' character.
mActivityTestRule.loadDataSync(
awContents,
mContentsClient.getOnPageFinishedHelper(),
"<html>test</html>",
"text/html",
false);
Assert.assertEquals("data:text/html,<html>test</html>", awContents.getLastCommittedUrl());
// A URL with one '#' character.
mActivityTestRule.loadDataSync(
awContents,
mContentsClient.getOnPageFinishedHelper(),
"<html>test#foo</html>",
"text/html",
false);
String expectedUrl =
encodeOctothorpes
? "data:text/html,<html>test%23foo</html>"
: "data:text/html,<html>test#foo</html>";
Assert.assertEquals(expectedUrl, awContents.getLastCommittedUrl());
// A URL with many '#' characters.
mActivityTestRule.loadDataSync(
awContents,
mContentsClient.getOnPageFinishedHelper(),
"<html>test#foo#bar#</html>",
"text/html",
false);
expectedUrl =
encodeOctothorpes
? "data:text/html,<html>test%23foo%23bar%23</html>"
: "data:text/html,<html>test#foo#bar#</html>";
Assert.assertEquals(expectedUrl, awContents.getLastCommittedUrl());
// An already encoded '#' character.
mActivityTestRule.loadDataSync(
awContents,
mContentsClient.getOnPageFinishedHelper(),
"<html>test%23foo</html>",
"text/html",
false);
Assert.assertEquals(
"data:text/html,<html>test%23foo</html>", awContents.getLastCommittedUrl());
// A URL with a valid fragment. Before Q, this must be manipulated so that it renders the
// same and still scrolls to the fragment location.
if (encodeOctothorpes) {
String contents = "<div style='height: 5000px'></div><a id='target'>Target</a>#target";
mActivityTestRule.loadDataSync(
awContents,
mContentsClient.getOnPageFinishedHelper(),
contents,
"text/html",
false);
Assert.assertEquals(
"data:text/html,<div style='height: 5000px'></div><a id='target'>Target</a>"
+ "%23target#target",
awContents.getLastCommittedUrl());
// TODO(smcgruer): I can physically see that this has scrolled on the test page, and
// have traced scrolling through PaintLayerScrollableArea, but I don't know how to check
// it.
}
}
@Test
@Feature({"AndroidWebView"})
@MediumTest
public void testLoadsJsModule() throws Throwable {
mActivityTestRule.startBrowserProcess();
AwTestContainerView testView =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
final AwContents awContents = testView.getAwContents();
AwSettings awSettings = mActivityTestRule.getAwSettingsOnUiThread(awContents);
// This test is specifically about relative file urls
awSettings.setAllowFileAccess(true);
awSettings.setAllowFileAccessFromFileURLs(true);
// This test runs some javascript to verify if it passes
AwActivityTestRule.enableJavaScriptOnUiThread(awContents);
// Using a future to wait to see if the js module was loaded or not.
// The page in the test will expect this object.
final SettableFuture<Boolean> fetchResultFuture = SettableFuture.create();
Object injectedObject =
new Object() {
@JavascriptInterface
public void success() {
fetchResultFuture.set(true);
}
@JavascriptInterface
public void error() {
fetchResultFuture.set(false);
}
};
AwActivityTestRule.addJavascriptInterfaceOnUiThread(
awContents, injectedObject, "injectedObject");
final String url = "file:///android_asset/page_with_module.html";
mActivityTestRule.loadUrlAsync(awContents, url);
Assert.assertTrue(AwActivityTestRule.waitForFuture(fetchResultFuture));
}
@Test
@Feature({"AndroidWebView"})
@SmallTest
public void testLoadUrlRecordsScheme_http() {
// No need to spin up a web server, since we don't care if the load ever succeeds.
final String httpUrlWithNoRealPage = "http://some.origin.test/some/path.html";
loadUrlAndCheckScheme(httpUrlWithNoRealPage, AwContents.UrlScheme.HTTP_SCHEME);
}
@Test
@Feature({"AndroidWebView"})
@SmallTest
public void testLoadUrlRecordsScheme_javascript() {
loadUrlAndCheckScheme(
"javascript:console.log('message')", AwContents.UrlScheme.JAVASCRIPT_SCHEME);
}
@Test
@Feature({"AndroidWebView"})
@SmallTest
public void testLoadUrlRecordsScheme_fileAndroidAsset() {
loadUrlAndCheckScheme(
"file:///android_asset/some/asset/page.html",
AwContents.UrlScheme.FILE_ANDROID_ASSET_SCHEME);
}
@Test
@Feature({"AndroidWebView"})
@SmallTest
public void testLoadUrlRecordsScheme_fileRegular() {
loadUrlAndCheckScheme("file:///some/path/on/disk.html", AwContents.UrlScheme.FILE_SCHEME);
}
@Test
@Feature({"AndroidWebView"})
@SmallTest
public void testLoadUrlRecordsScheme_data() {
loadUrlAndCheckScheme(
"data:text/html,<html><body>foo</body></html>", AwContents.UrlScheme.DATA_SCHEME);
}
@Test
@Feature({"AndroidWebView"})
@SmallTest
public void testLoadUrlRecordsScheme_blank() {
loadUrlAndCheckScheme("about:blank", AwContents.UrlScheme.EMPTY);
}
private void loadUrlAndCheckScheme(String url, @AwContents.UrlScheme int expectedSchemeEnum) {
mActivityTestRule.startBrowserProcess();
AwTestContainerView testView =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
final AwContents awContents = testView.getAwContents();
HistogramWatcher histogramExpectation =
HistogramWatcher.newSingleRecordWatcher(
AwContents.LOAD_URL_SCHEME_HISTOGRAM_NAME, expectedSchemeEnum);
// Note: we use async because not all loads emit onPageFinished. This relies on the UMA
// metric being logged in the synchronous part of loadUrl().
mActivityTestRule.loadUrlAsync(awContents, url);
histogramExpectation.assertExpected();
}
@Test
@Feature({"AndroidWebView"})
@SmallTest
public void testFindAllAsyncEmptySearchString() {
mActivityTestRule.startBrowserProcess();
AwTestContainerView testView =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
final AwContents awContents = testView.getAwContents();
try {
awContents.findAllAsync(null);
Assert.fail("A null searchString should cause an exception to be thrown");
} catch (IllegalArgumentException e) {
// expected
}
}
@Test
@Feature({"AndroidWebView"})
@SmallTest
public void testInsertNullVisualStateCallback() {
mActivityTestRule.startBrowserProcess();
AwTestContainerView testView =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
final AwContents awContents = testView.getAwContents();
try {
awContents.insertVisualStateCallback(0, null);
Assert.fail("A null VisualStateCallback should cause an exception to be thrown");
} catch (IllegalArgumentException e) {
// expected
}
}
// This test verifies that Private Network Access' secure context
// restriction (feature flag BlockInsecurePrivateNetworkRequests) does not
// apply to Webview: insecure private network requests are allowed.
//
// This is a regression test for crbug.com/1255675.
@Test
@Feature({"AndroidWebView"})
@CommandLineFlags.Add(ContentSwitches.HOST_RESOLVER_RULES + "=MAP * 127.0.0.1")
@SmallTest
public void testInsecurePrivateNetworkAccess() throws Throwable {
mActivityTestRule.startBrowserProcess();
final AwTestContainerView testContainer =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
final AwContents awContents = testContainer.getAwContents();
AwActivityTestRule.enableJavaScriptOnUiThread(awContents);
// This SettableFuture and its accompanying injected object allows us to
// synchronize on the fetch result.
final SettableFuture<Boolean> fetchResultFuture = SettableFuture.create();
Object injectedObject =
new Object() {
@JavascriptInterface
public void success() {
fetchResultFuture.set(true);
}
@JavascriptInterface
public void error() {
fetchResultFuture.set(false);
}
};
AwActivityTestRule.addJavascriptInterfaceOnUiThread(
awContents, injectedObject, "injectedObject");
EmbeddedTestServer testServer =
EmbeddedTestServer.createAndStartServer(
InstrumentationRegistry.getInstrumentation().getContext());
// Need to avoid http://localhost, which is considered secure, so we
// use http://foo.test, which resolves to 127.0.0.1 thanks to the
// host resolver rules command-line flag.
//
// The resulting document is a non-secure context in the public IP
// address space. If the secure context restriction were applied, it
// would not be allowed to fetch subresources from localhost.
String url =
testServer.getURLWithHostName(
"foo.test", "/set-header?Content-Security-Policy: treat-as-public-address");
mActivityTestRule.loadUrlSync(awContents, mContentsClient.getOnPageFinishedHelper(), url);
// Fetch a subresource from the same server, whose IP address is still
// 127.0.0.1, thus belonging to the local IP address space.
// This should succeed.
mActivityTestRule.executeJavaScriptAndWaitForResult(
awContents,
mContentsClient,
"fetch('/defaultresponse')"
+ ".then(() => { injectedObject.success() })"
+ ".catch((err) => { "
+ " console.log(err); "
+ " injectedObject.error(); "
+ "})");
Assert.assertTrue(AwActivityTestRule.waitForFuture(fetchResultFuture));
}
private static final String HELLO_WORLD_URL = "/android_webview/test/data/hello_world.html";
private static final String HELLO_WORLD_TITLE = "Hello, World!";
private static final String WEBUI_URL = "chrome://safe-browsing";
private static final String WEBUI_TITLE = "Safe Browsing";
// Check that we can navigate between a regular web page and a WebUI page
// that's available on AW (chrome://safe-browsing), and that the WebUI page
// loads in its own locked renderer process when in multi-process mode.
@Test
@Feature({"AndroidWebView"})
@SmallTest
@OnlyRunIn(MULTI_PROCESS)
public void testWebUIUsesDedicatedProcessInMultiProcessMode() throws Throwable {
mActivityTestRule.startBrowserProcess();
AwTestContainerView testView =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
final AwContents awContents = testView.getAwContents();
AwActivityTestRule.enableJavaScriptOnUiThread(awContents);
EmbeddedTestServer testServer =
EmbeddedTestServer.createAndStartServer(
InstrumentationRegistry.getInstrumentation().getContext());
final String pageUrl = testServer.getURL(HELLO_WORLD_URL);
mActivityTestRule.loadUrlSync(
awContents, mContentsClient.getOnPageFinishedHelper(), pageUrl);
Assert.assertEquals(HELLO_WORLD_TITLE, mActivityTestRule.getTitleOnUiThread(awContents));
final AwRenderProcess rendererProcess1 = getRenderProcessOnUiThread(awContents);
Assert.assertNotNull(rendererProcess1);
// Until AW gets site isolation, ordinary web content should not be
// locked to origin.
boolean isLocked =
TestThreadUtils.runOnUiThreadBlocking(
() -> rendererProcess1.isProcessLockedToSiteForTesting());
Assert.assertFalse("Initial renderer process should not be locked", isLocked);
mActivityTestRule.loadUrlSync(
awContents, mContentsClient.getOnPageFinishedHelper(), WEBUI_URL);
Assert.assertEquals(WEBUI_TITLE, mActivityTestRule.getTitleOnUiThread(awContents));
final AwRenderProcess webuiProcess = getRenderProcessOnUiThread(awContents);
Assert.assertNotEquals(rendererProcess1, webuiProcess);
// WebUI pages should be locked to origin even on AW.
isLocked =
TestThreadUtils.runOnUiThreadBlocking(
() -> webuiProcess.isProcessLockedToSiteForTesting());
Assert.assertTrue("WebUI process should be locked", isLocked);
mActivityTestRule.loadUrlSync(
awContents, mContentsClient.getOnPageFinishedHelper(), pageUrl);
final AwRenderProcess rendererProcess2 = getRenderProcessOnUiThread(awContents);
Assert.assertEquals(HELLO_WORLD_TITLE, mActivityTestRule.getTitleOnUiThread(awContents));
Assert.assertNotEquals(rendererProcess2, webuiProcess);
isLocked =
TestThreadUtils.runOnUiThreadBlocking(
() -> rendererProcess2.isProcessLockedToSiteForTesting());
Assert.assertFalse("Final renderer process should not be locked", isLocked);
}
// In single-process mode, navigations to WebUI should work, but WebUI does
// not gets process-isolated.
@Test
@Feature({"AndroidWebView"})
@SmallTest
@OnlyRunIn(SINGLE_PROCESS)
public void testWebUILoadsWithoutProcessIsolationInSingleProcessMode() throws Throwable {
mActivityTestRule.startBrowserProcess();
AwTestContainerView testView =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
final AwContents awContents = testView.getAwContents();
AwActivityTestRule.enableJavaScriptOnUiThread(awContents);
EmbeddedTestServer testServer =
EmbeddedTestServer.createAndStartServer(
InstrumentationRegistry.getInstrumentation().getContext());
final String pageUrl = testServer.getURL(HELLO_WORLD_URL);
mActivityTestRule.loadUrlSync(
awContents, mContentsClient.getOnPageFinishedHelper(), pageUrl);
Assert.assertEquals(HELLO_WORLD_TITLE, mActivityTestRule.getTitleOnUiThread(awContents));
final AwRenderProcess rendererProcess1 = getRenderProcessOnUiThread(awContents);
Assert.assertNull(rendererProcess1);
mActivityTestRule.loadUrlSync(
awContents, mContentsClient.getOnPageFinishedHelper(), WEBUI_URL);
Assert.assertEquals(WEBUI_TITLE, mActivityTestRule.getTitleOnUiThread(awContents));
final AwRenderProcess webuiProcess = getRenderProcessOnUiThread(awContents);
Assert.assertNull(webuiProcess);
}
@Test
@Feature({"AndroidWebView"})
@MediumTest
@OnlyRunIn(MULTI_PROCESS)
@CommandLineFlags.Add(ContentSwitches.SITE_PER_PROCESS)
@DisabledTest(message = "https://crbug.com/1246585")
public void testOutOfProcessIframeSmokeTest() throws Throwable {
mActivityTestRule.startBrowserProcess();
AwTestContainerView testView =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
final AwContents awContents = testView.getAwContents();
TestWebServer webServer = TestWebServer.start();
try {
// Destination iframe has blue color.
final String iframeDestinationPath =
webServer.setResponse(
"/iframe_destination.html",
"<html><body style=\"background-color:rgb(0,0,255);\"></body></html>",
null);
// Initial iframe has red color with a full-page link to navigate to destination.
final String iframePath =
webServer.setResponse(
"/iframe.html",
"<html><body style=\"background-color:rgb(255,0,0);\">"
+ "<a href=\""
+ iframeDestinationPath
+ "\" "
+ "style=\"width:100%;height:100%;display:block;\"></a>"
+ "</body></html>",
null);
// Main frame has green color at the top half, and iframe in the bottom half.
final String pageHtml =
"<html><body><div"
+ " style=\"width:100%;height:50%;background-color:rgb(0,255,0);\"></div><iframe"
+ " style=\"width:100%;height:50%;\" src=\""
+ iframePath
+ "\"></iframe>"
+ "</body></html>";
// Iframes are loaded with origin of the test server, and the main page is loaded with
// origin http://foo.bar. This ensures that the main and iframe are different renderer
// processes when site isolation is enabled.
mActivityTestRule.loadDataWithBaseUrlSync(
awContents,
mContentsClient.getOnPageFinishedHelper(),
pageHtml,
"text/html",
false,
"http://foo.bar",
null);
// Check initial iframe is displayed.
int[] expectedQuadrantColors = {
Color.rgb(0, 255, 0),
Color.rgb(0, 255, 0),
Color.rgb(255, 0, 0),
Color.rgb(255, 0, 0),
};
GraphicsTestUtils.pollForQuadrantColors(testView, expectedQuadrantColors);
assertThat(RenderProcessHostUtils.getCurrentRenderProcessCount(), greaterThan(1));
// Click iframe to navigate. This exercises hit testing code paths.
TestThreadUtils.runOnUiThreadBlocking(
() -> {
int width = testView.getWidth();
int height = testView.getHeight();
TouchCommon.singleClickView(testView, width / 2, height * 3 / 4);
});
// Check destination iframe is displayed.
expectedQuadrantColors =
new int[] {
Color.rgb(0, 255, 0),
Color.rgb(0, 255, 0),
Color.rgb(0, 0, 255),
Color.rgb(0, 0, 255),
};
GraphicsTestUtils.pollForQuadrantColors(testView, expectedQuadrantColors);
assertThat(RenderProcessHostUtils.getCurrentRenderProcessCount(), greaterThan(1));
} finally {
webServer.shutdown();
}
}
private class FakePostDelayedTask implements BiFunction<Runnable, Long, Void> {
@Override
public Void apply(Runnable runnable, Long delay) {
long time = TimeUtils.uptimeMillis() + delay;
mTasks.add(new Pair<Runnable, Long>(runnable, time));
return null;
}
public void fastForwardBy(long delay) {
mFakeTimeTestRule.advanceMillis(delay);
final long now = TimeUtils.uptimeMillis();
Predicate<Pair<Runnable, Long>> deadlinePassed =
(Pair<Runnable, Long> p) -> {
return p.second <= now;
};
// Tasks running can post other tasks, do it in stages to prevent concurrent
// modification errors.
var toRun = new ArrayList<Pair<Runnable, Long>>();
for (var p : mTasks) {
if (deadlinePassed.test(p)) {
toRun.add(p);
}
}
mTasks.removeIf(deadlinePassed);
for (var p : toRun) {
p.first.run();
}
}
public int getPendingTasksCount() {
return mTasks.size();
}
private List<Pair<Runnable, Long>> mTasks = new ArrayList<Pair<Runnable, Long>>();
}
@Test
@Feature({"AndroidWebView"})
@MediumTest
@Features.EnableFeatures({AwFeatures.WEBVIEW_CLEAR_FUNCTOR_IN_BACKGROUND})
public void testClearDrawFunctorInBackground() throws Throwable {
mActivityTestRule.startBrowserProcess();
AwTestContainerView testView =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
final AwContents awContents = testView.getAwContents();
AwContents.resetRecordMemoryForTesting();
// Load a page to ensure that at least one draw has happened.
doHardwareRenderingSmokeTest(testView);
Assert.assertTrue(awContents.hasDrawFunctor());
TestThreadUtils.runOnUiThreadBlocking(
() -> {
var postTask = new FakePostDelayedTask();
awContents.setPostDelayedTaskForTesting(postTask);
awContents.onWindowVisibilityChanged(View.INVISIBLE);
// Delayed release task.
Assert.assertEquals(1, postTask.getPendingTasksCount());
postTask.fastForwardBy(AwContents.FUNCTOR_RECLAIM_DELAY_MS);
// Metrics task is still pending.
Assert.assertEquals(1, postTask.getPendingTasksCount());
Assert.assertFalse(awContents.hasDrawFunctor());
awContents.onWindowVisibilityChanged(View.VISIBLE);
Assert.assertFalse(awContents.hasDrawFunctor());
// Metrics task will not report histograms because we went back to foreground in
// the meantime.
var histograms =
HistogramWatcher.newBuilder()
.expectNoRecords(AwContents.PSS_HISTOGRAM)
.expectNoRecords(AwContents.PRIVATE_DIRTY_HISTOGRAM)
.build();
postTask.fastForwardBy(AwContents.METRICS_COLLECTION_DELAY_MS);
Assert.assertEquals(0, postTask.getPendingTasksCount());
histograms.assertExpected();
});
// Rendering still works.
doHardwareRenderingSmokeTest(testView, 42, 42, 42);
TestThreadUtils.runOnUiThreadBlocking(
() -> {
Assert.assertTrue(awContents.hasDrawFunctor());
});
}
@Test
@Feature({"AndroidWebView"})
@MediumTest
@Features.EnableFeatures({AwFeatures.WEBVIEW_CLEAR_FUNCTOR_IN_BACKGROUND})
public void testClearDrawFunctorInBackgroundMultipleTransitions() throws Throwable {
mActivityTestRule.startBrowserProcess();
AwContents.resetRecordMemoryForTesting();
AwTestContainerView testView =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
final AwContents awContents = testView.getAwContents();
// Load a page to ensure that at least one draw has happened.
doHardwareRenderingSmokeTest(testView);
Assert.assertTrue(awContents.hasDrawFunctor());
TestThreadUtils.runOnUiThreadBlocking(
() -> {
var postTask = new FakePostDelayedTask();
awContents.setPostDelayedTaskForTesting(postTask);
awContents.onWindowVisibilityChanged(View.INVISIBLE);
Assert.assertEquals(1, postTask.getPendingTasksCount());
postTask.fastForwardBy(AwContents.FUNCTOR_RECLAIM_DELAY_MS / 2);
awContents.onWindowVisibilityChanged(View.VISIBLE);
awContents.onWindowVisibilityChanged(View.INVISIBLE);
Assert.assertEquals(1, postTask.getPendingTasksCount());
postTask.fastForwardBy(AwContents.FUNCTOR_RECLAIM_DELAY_MS / 2);
// Not enough continuous time in background.
Assert.assertTrue(awContents.hasDrawFunctor());
// But there is still a task pending.
Assert.assertEquals(1, postTask.getPendingTasksCount());
// Multiple transitions do not post multiple tasks.
awContents.onWindowVisibilityChanged(View.VISIBLE);
awContents.onWindowVisibilityChanged(View.INVISIBLE);
Assert.assertEquals(1, postTask.getPendingTasksCount());
// Functor is reclaimed after enough continuous time in background.
postTask.fastForwardBy(AwContents.FUNCTOR_RECLAIM_DELAY_MS);
Assert.assertFalse(awContents.hasDrawFunctor());
// Metrics task.
var histograms =
HistogramWatcher.newBuilder()
.expectAnyRecord(AwContents.PSS_HISTOGRAM)
.expectAnyRecord(AwContents.PRIVATE_DIRTY_HISTOGRAM)
.build();
Assert.assertEquals(1, postTask.getPendingTasksCount());
postTask.fastForwardBy(AwContents.METRICS_COLLECTION_DELAY_MS);
Assert.assertEquals(0, postTask.getPendingTasksCount());
histograms.assertExpected();
});
// Not testing rendering here, because all the back and forth advanced the virtual clock too
// much, the test would time out.
}
@Test
@Feature({"AndroidWebView"})
@MediumTest
@Features.DisableFeatures({AwFeatures.WEBVIEW_CLEAR_FUNCTOR_IN_BACKGROUND})
public void testDoeNotClearDrawFunctorInBackground() throws Throwable {
mActivityTestRule.startBrowserProcess();
AwContents.resetRecordMemoryForTesting();
AwTestContainerView testView =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
final AwContents awContents = testView.getAwContents();
// Load a page to ensure that at least one draw has happened.
doHardwareRenderingSmokeTest(testView);
Assert.assertTrue(awContents.hasDrawFunctor());
TestThreadUtils.runOnUiThreadBlocking(
() -> {
var postTask = new FakePostDelayedTask();
awContents.setPostDelayedTaskForTesting(postTask);
awContents.onWindowVisibilityChanged(View.INVISIBLE);
// Background cleanup task is posted even when the feature is disabled.
Assert.assertEquals(1, postTask.getPendingTasksCount());
postTask.fastForwardBy(AwContents.FUNCTOR_RECLAIM_DELAY_MS);
// But the functor is not cleared.
Assert.assertTrue(awContents.hasDrawFunctor());
// Metrics task.
var histograms =
HistogramWatcher.newBuilder()
.expectAnyRecord(AwContents.PSS_HISTOGRAM)
.expectAnyRecord(AwContents.PRIVATE_DIRTY_HISTOGRAM)
.build();
Assert.assertEquals(1, postTask.getPendingTasksCount());
postTask.fastForwardBy(AwContents.METRICS_COLLECTION_DELAY_MS);
Assert.assertEquals(0, postTask.getPendingTasksCount());
histograms.assertExpected();
awContents.onWindowVisibilityChanged(View.VISIBLE);
Assert.assertEquals(0, postTask.getPendingTasksCount());
Assert.assertTrue(awContents.hasDrawFunctor());
});
// Rendering still works.
doHardwareRenderingSmokeTest(testView, 42, 42, 42);
TestThreadUtils.runOnUiThreadBlocking(
() -> {
Assert.assertTrue(awContents.hasDrawFunctor());
});
}
@Test
@Feature({"AndroidWebView"})
@MediumTest
@Features.EnableFeatures({AwFeatures.WEBVIEW_CLEAR_FUNCTOR_IN_BACKGROUND})
public void testClearFunctorOnBackgroundMemorySignal() throws Throwable {
mActivityTestRule.startBrowserProcess();
AwContents.resetRecordMemoryForTesting();
AwTestContainerView testView =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
final AwContents awContents = testView.getAwContents();
// Load a page to ensure that at least one draw has happened.
doHardwareRenderingSmokeTest(testView);
Assert.assertTrue(awContents.hasDrawFunctor());
TestThreadUtils.runOnUiThreadBlocking(
() -> {
var postTask = new FakePostDelayedTask();
awContents.setPostDelayedTaskForTesting(postTask);
// Not required to happen in background, but this is how the notification is
// dispatched in real code.
awContents.onWindowVisibilityChanged(View.INVISIBLE);
Assert.assertTrue(awContents.hasDrawFunctor());
Assert.assertEquals(1, postTask.getPendingTasksCount());
awContents.onTrimMemory(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND);
Assert.assertFalse(awContents.hasDrawFunctor());
// Metrics task.
var histograms =
HistogramWatcher.newBuilder()
.expectAnyRecord(AwContents.PSS_HISTOGRAM)
.expectAnyRecord(AwContents.PRIVATE_DIRTY_HISTOGRAM)
.build();
Assert.assertEquals(2, postTask.getPendingTasksCount());
postTask.fastForwardBy(AwContents.METRICS_COLLECTION_DELAY_MS);
Assert.assertEquals(1, postTask.getPendingTasksCount());
histograms.assertExpected();
awContents.onWindowVisibilityChanged(View.VISIBLE);
Assert.assertFalse(awContents.hasDrawFunctor());
});
// Rendering still works.
doHardwareRenderingSmokeTest(testView, 42, 42, 42);
TestThreadUtils.runOnUiThreadBlocking(
() -> {
Assert.assertTrue(awContents.hasDrawFunctor());
});
}
@Test
@Feature({"AndroidWebView"})
@MediumTest
@Features.DisableFeatures({AwFeatures.WEBVIEW_CLEAR_FUNCTOR_IN_BACKGROUND})
public void testMetricsRecordingIsThrottled() throws Throwable {
mActivityTestRule.startBrowserProcess();
AwContents.resetRecordMemoryForTesting();
AwTestContainerView testView =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
final AwContents awContents = testView.getAwContents();
// Load a page to ensure that at least one draw has happened.
doHardwareRenderingSmokeTest(testView);
Assert.assertTrue(awContents.hasDrawFunctor());
TestThreadUtils.runOnUiThreadBlocking(
() -> {
var postTask = new FakePostDelayedTask();
awContents.setPostDelayedTaskForTesting(postTask);
// Not required to happen in background, but this is how the notification is
// dispatched in real code.
awContents.onWindowVisibilityChanged(View.INVISIBLE);
awContents.onTrimMemory(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND);
Assert.assertTrue(awContents.hasDrawFunctor());
// Metrics task.
var histograms =
HistogramWatcher.newBuilder()
.expectAnyRecord(AwContents.PSS_HISTOGRAM)
.expectAnyRecord(AwContents.PRIVATE_DIRTY_HISTOGRAM)
.build();
Assert.assertEquals(2, postTask.getPendingTasksCount());
postTask.fastForwardBy(AwContents.METRICS_COLLECTION_DELAY_MS);
Assert.assertEquals(1, postTask.getPendingTasksCount());
histograms.assertExpected();
postTask.fastForwardBy(
AwContents.FUNCTOR_RECLAIM_DELAY_MS
- AwContents.METRICS_COLLECTION_DELAY_MS);
Assert.assertEquals(1, postTask.getPendingTasksCount());
// Metrics are not recorded this time, not enough time has passed.
histograms =
HistogramWatcher.newBuilder()
.expectNoRecords(AwContents.PSS_HISTOGRAM)
.expectNoRecords(AwContents.PRIVATE_DIRTY_HISTOGRAM)
.build();
postTask.fastForwardBy(AwContents.METRICS_COLLECTION_DELAY_MS);
Assert.assertEquals(0, postTask.getPendingTasksCount());
histograms.assertExpected();
});
}
// Disables hardware acceleration and ensures that there is no crash in the code that adds and
// removes frame metrics listener. This code should do nothing when hardware acceleration is
// disabled.
@Test
@DisableHardwareAcceleration
@SmallTest
@Feature({"AndroidWebView"})
@Features.EnableFeatures({BaseFeatures.COLLECT_ANDROID_FRAME_TIMELINE_METRICS})
public void testNoCrashWithoutHardwareAcceleration() throws Throwable {
mActivityTestRule.startBrowserProcess();
AwContents.resetRecordMemoryForTesting();
AwTestContainerView testView =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
final AwContents awContents = testView.getAwContents();
// Frame metrics listener is detached when AwContents becomes invisible.
TestThreadUtils.runOnUiThreadBlocking(
() -> {
awContents.onWindowVisibilityChanged(View.INVISIBLE);
});
Assert.assertFalse(testView.isBackedByHardwareView());
}
private static final class InjectingPlatformServiceBridge extends PlatformServiceBridge {
private static final String DEMO_JAVASCRIPT =
"""
function injectionDemo() {
return "injected";
}
""";
@Override
public void injectPlatformJsInterfaces(
@NonNull Context context, @NonNull AwContentsWrapper receiver) {
receiver.addDocumentStartJavaScript(DEMO_JAVASCRIPT, new String[] {"*"});
}
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
@Features.EnableFeatures({AwFeatures.WEBVIEW_INJECT_PLATFORM_JS_APIS})
public void testPlatformJsInjectedIfEnabled() throws Exception {
mActivityTestRule.startBrowserProcess();
InjectingPlatformServiceBridge bridge = new InjectingPlatformServiceBridge();
PlatformServiceBridge.injectInstance(bridge);
AwTestContainerView testView =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
final AwContents awContents = testView.getAwContents();
AwActivityTestRule.enableJavaScriptOnUiThread(awContents);
mActivityTestRule.loadUrlSync(
awContents, mContentsClient.getOnPageFinishedHelper(), "about:blank");
String result =
mActivityTestRule.executeJavaScriptAndWaitForResult(
awContents, mContentsClient, "injectionDemo();");
Assert.assertEquals("\"injected\"", result);
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
@Features.DisableFeatures({AwFeatures.WEBVIEW_INJECT_PLATFORM_JS_APIS})
public void testPlatformJsNotInjectedIfDisabled() throws Exception {
mActivityTestRule.startBrowserProcess();
InjectingPlatformServiceBridge bridge = new InjectingPlatformServiceBridge();
PlatformServiceBridge.injectInstance(bridge);
AwTestContainerView testView =
mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
final AwContents awContents = testView.getAwContents();
AwActivityTestRule.enableJavaScriptOnUiThread(awContents);
mActivityTestRule.loadUrlSync(
awContents, mContentsClient.getOnPageFinishedHelper(), "about:blank");
String result =
mActivityTestRule.executeJavaScriptAndWaitForResult(
awContents,
mContentsClient,
"window.injectionDemo ? 'exists unexpectedly' : 'does not exist';");
Assert.assertEquals("\"does not exist\"", result);
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
@SuppressLint("WrongConstant")
// crbug.com/1493531
public void testInvalidTouchEventIsRemoved() {
mActivityTestRule.startBrowserProcess();
AwContents awContents =
mActivityTestRule
.createAwTestContainerViewOnMainSync(mContentsClient)
.getAwContents();
MotionEvent.PointerProperties properties = new MotionEvent.PointerProperties();
properties.id = 1;
properties.toolType = 20;
// Create a motion event with one pointer with tool type set to 20.
MotionEvent event =
MotionEvent.obtain(
0L,
0L,
0,
1,
new MotionEvent.PointerProperties[] {properties},
new MotionEvent.PointerCoords[] {new MotionEvent.PointerCoords()},
0,
0,
0f,
0f,
0,
0,
0,
0);
HistogramWatcher watcher =
HistogramWatcher.newSingleRecordWatcher("Input.ToolType.Android", 20);
Assert.assertFalse(awContents.onTouchEvent(event));
watcher.assertExpected();
}
}