blob: fe3e31e8165e7d211d9f34fc1255aa3ba61788ff [file] [log] [blame]
// Copyright 2017 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.base.test.util.ScalableTimeout.scaleTimeout;
import android.content.Context;
import android.content.Intent;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.lifecycle.Stage;
import android.util.AndroidRuntimeException;
import android.util.Base64;
import android.view.ViewGroup;
import org.junit.Assert;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.chromium.android_webview.AwBrowserContext;
import org.chromium.android_webview.AwBrowserProcess;
import org.chromium.android_webview.AwContents;
import org.chromium.android_webview.AwContents.DependencyFactory;
import org.chromium.android_webview.AwContents.InternalAccessDelegate;
import org.chromium.android_webview.AwContents.NativeDrawFunctorFactory;
import org.chromium.android_webview.AwContentsClient;
import org.chromium.android_webview.AwSettings;
import org.chromium.android_webview.test.util.GraphicsTestUtils;
import org.chromium.android_webview.test.util.JSUtils;
import org.chromium.base.Log;
import org.chromium.base.test.BaseActivityTestRule;
import org.chromium.base.test.util.ApplicationTestUtils;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.base.test.util.InMemorySharedPreferences;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.test.util.TestCallbackHelperContainer.OnPageFinishedHelper;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.net.test.util.TestWebServer;
import java.lang.annotation.Annotation;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** Custom ActivityTestRunner for WebView instrumentation tests */
public class AwActivityTestRule extends BaseActivityTestRule<AwTestRunnerActivity> {
public static final long WAIT_TIMEOUT_MS = scaleTimeout(15000L);
public static final int CHECK_INTERVAL = 100;
private static final String TAG = "AwActivityTestRule";
private static final Pattern MAYBE_QUOTED_STRING = Pattern.compile("^(\"?)(.*)\\1$");
private static boolean sBrowserProcessStarted;
/**
* An interface to call onCreateWindow(AwContents).
*/
public interface OnCreateWindowHandler {
/** This will be called when a new window pops up from the current webview. */
public boolean onCreateWindow(AwContents awContents);
}
private Description mCurrentTestDescription;
// The browser context needs to be a process-wide singleton.
private AwBrowserContext mBrowserContext;
private List<WeakReference<AwContents>> mAwContentsDestroyedInTearDown = new ArrayList<>();
public AwActivityTestRule() {
super(AwTestRunnerActivity.class);
}
@Override
public Statement apply(final Statement base, Description description) {
mCurrentTestDescription = description;
return super.apply(new Statement() {
@Override
public void evaluate() throws Throwable {
setUp();
base.evaluate();
tearDown();
}
}, description);
}
public void setUp() {
if (needsAwBrowserContextCreated()) {
createAwBrowserContext();
}
if (needsBrowserProcessStarted()) {
startBrowserProcess();
} else {
assert !sBrowserProcessStarted
: "needsBrowserProcessStarted false and @Batch are incompatible";
}
}
public void tearDown() {
if (!needsAwContentsCleanup()) return;
TestThreadUtils.runOnUiThreadBlocking(() -> {
for (WeakReference<AwContents> awContentsRef : mAwContentsDestroyedInTearDown) {
AwContents awContents = awContentsRef.get();
if (awContents == null) continue;
awContents.destroy();
}
});
// Flush the UI queue since destroy posts again to UI thread.
TestThreadUtils.runOnUiThreadBlocking(() -> { mAwContentsDestroyedInTearDown.clear(); });
}
public boolean needsHideActionBar() {
return false;
}
private Intent getLaunchIntent() {
if (needsHideActionBar()) {
Intent intent = getActivityIntent();
intent.putExtra(AwTestRunnerActivity.FLAG_HIDE_ACTION_BAR, true);
return intent;
}
return null;
}
@Override
public void launchActivity(Intent intent) {
if (getActivity() != null) return;
super.launchActivity(intent);
ApplicationTestUtils.waitForActivityState(getActivity(), Stage.RESUMED);
}
public AwTestRunnerActivity launchActivity() {
launchActivity(getLaunchIntent());
return getActivity();
}
public AwBrowserContext createAwBrowserContextOnUiThread(InMemorySharedPreferences prefs) {
// Native pointer is initialized later in startBrowserProcess if needed.
return new AwBrowserContext(prefs, 0, true);
}
public TestDependencyFactory createTestDependencyFactory() {
return new TestDependencyFactory();
}
/**
* Override this to return false if the test doesn't want to create an
* AwBrowserContext automatically.
*/
public boolean needsAwBrowserContextCreated() {
return true;
}
/**
* Override this to return false if the test doesn't want the browser
* startup sequence to be run automatically.
*
* @return Whether the instrumentation test requires the browser process to
* already be started.
*/
public boolean needsBrowserProcessStarted() {
return true;
}
/**
* Override this to return false if test doesn't need all AwContents to be
* destroyed explicitly after the test.
*/
public boolean needsAwContentsCleanup() {
return true;
}
public void createAwBrowserContext() {
if (mBrowserContext != null) {
throw new AndroidRuntimeException("There should only be one browser context.");
}
launchActivity(); // The Activity must be launched in order to load native code
final InMemorySharedPreferences prefs = new InMemorySharedPreferences();
TestThreadUtils.runOnUiThreadBlockingNoException(
() -> mBrowserContext = createAwBrowserContextOnUiThread(prefs));
}
public void startBrowserProcess() {
doStartBrowserProcess(false);
}
public void startBrowserProcessWithVulkan() {
doStartBrowserProcess(true);
}
private void doStartBrowserProcess(boolean useVulkan) {
// The Activity must be launched in order for proper webview statics to be setup.
launchActivity();
if (!sBrowserProcessStarted) {
sBrowserProcessStarted = true;
TestThreadUtils.runOnUiThreadBlocking(() -> {
AwTestContainerView.installDrawFnFunctionTable(useVulkan);
AwBrowserProcess.start();
});
}
if (mBrowserContext != null) {
TestThreadUtils.runOnUiThreadBlocking(
() -> mBrowserContext.setNativePointer(
AwBrowserContext.getDefault().getNativePointer()));
}
}
public void runOnUiThread(Runnable r) {
TestThreadUtils.runOnUiThreadBlocking(r);
}
public static void enableJavaScriptOnUiThread(final AwContents awContents) {
TestThreadUtils.runOnUiThreadBlocking(
() -> awContents.getSettings().setJavaScriptEnabled(true));
}
public static void setNetworkAvailableOnUiThread(
final AwContents awContents, final boolean networkUp) {
TestThreadUtils.runOnUiThreadBlocking(() -> awContents.setNetworkAvailable(networkUp));
}
/**
* Loads url on the UI thread and blocks until onPageFinished is called.
*/
public void loadUrlSync(final AwContents awContents, CallbackHelper onPageFinishedHelper,
final String url) throws Exception {
loadUrlSync(awContents, onPageFinishedHelper, url, null);
}
public void loadUrlSync(final AwContents awContents, CallbackHelper onPageFinishedHelper,
final String url, final Map<String, String> extraHeaders) throws Exception {
int currentCallCount = onPageFinishedHelper.getCallCount();
loadUrlAsync(awContents, url, extraHeaders);
onPageFinishedHelper.waitForCallback(
currentCallCount, 1, WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
public void loadUrlSyncAndExpectError(final AwContents awContents,
CallbackHelper onPageFinishedHelper, CallbackHelper onReceivedErrorHelper,
final String url) throws Exception {
int onErrorCallCount = onReceivedErrorHelper.getCallCount();
int onFinishedCallCount = onPageFinishedHelper.getCallCount();
loadUrlAsync(awContents, url);
onReceivedErrorHelper.waitForCallback(
onErrorCallCount, 1, WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
onPageFinishedHelper.waitForCallback(
onFinishedCallCount, 1, WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
/**
* Loads url on the UI thread but does not block.
*/
public void loadUrlAsync(final AwContents awContents, final String url) {
loadUrlAsync(awContents, url, null);
}
public void loadUrlAsync(
final AwContents awContents, final String url, final Map<String, String> extraHeaders) {
TestThreadUtils.runOnUiThreadBlocking(() -> awContents.loadUrl(url, extraHeaders));
}
/**
* Posts url on the UI thread and blocks until onPageFinished is called.
*/
public void postUrlSync(final AwContents awContents, CallbackHelper onPageFinishedHelper,
final String url, byte[] postData) throws Exception {
int currentCallCount = onPageFinishedHelper.getCallCount();
postUrlAsync(awContents, url, postData);
onPageFinishedHelper.waitForCallback(
currentCallCount, 1, WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
/**
* Loads url on the UI thread but does not block.
*/
public void postUrlAsync(final AwContents awContents, final String url, byte[] postData) {
class PostUrl implements Runnable {
byte[] mPostData;
public PostUrl(byte[] postData) {
mPostData = postData;
}
@Override
public void run() {
awContents.postUrl(url, mPostData);
}
}
TestThreadUtils.runOnUiThreadBlocking(new PostUrl(postData));
}
/**
* Loads data on the UI thread and blocks until onPageFinished is called.
*/
public void loadDataSync(final AwContents awContents, CallbackHelper onPageFinishedHelper,
final String data, final String mimeType, final boolean isBase64Encoded)
throws Exception {
int currentCallCount = onPageFinishedHelper.getCallCount();
loadDataAsync(awContents, data, mimeType, isBase64Encoded);
onPageFinishedHelper.waitForCallback(
currentCallCount, 1, WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
public void loadHtmlSync(final AwContents awContents, CallbackHelper onPageFinishedHelper,
final String html) throws Throwable {
int currentCallCount = onPageFinishedHelper.getCallCount();
final String encodedData = Base64.encodeToString(html.getBytes(), Base64.NO_PADDING);
loadDataSync(awContents, onPageFinishedHelper, encodedData, "text/html", true);
}
public void loadDataSyncWithCharset(final AwContents awContents,
CallbackHelper onPageFinishedHelper, final String data, final String mimeType,
final boolean isBase64Encoded, final String charset) throws Exception {
int currentCallCount = onPageFinishedHelper.getCallCount();
TestThreadUtils.runOnUiThreadBlocking(
() -> awContents.loadUrl(LoadUrlParams.createLoadDataParams(
data, mimeType, isBase64Encoded, charset)));
onPageFinishedHelper.waitForCallback(
currentCallCount, 1, WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
/**
* Loads data on the UI thread but does not block.
*/
public void loadDataAsync(final AwContents awContents, final String data, final String mimeType,
final boolean isBase64Encoded) {
TestThreadUtils.runOnUiThreadBlocking(
() -> awContents.loadData(data, mimeType, isBase64Encoded ? "base64" : null));
}
public void loadDataWithBaseUrlSync(final AwContents awContents,
CallbackHelper onPageFinishedHelper, final String data, final String mimeType,
final boolean isBase64Encoded, final String baseUrl, final String historyUrl)
throws Throwable {
int currentCallCount = onPageFinishedHelper.getCallCount();
loadDataWithBaseUrlAsync(awContents, data, mimeType, isBase64Encoded, baseUrl, historyUrl);
onPageFinishedHelper.waitForCallback(
currentCallCount, 1, WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
public void loadDataWithBaseUrlAsync(final AwContents awContents, final String data,
final String mimeType, final boolean isBase64Encoded, final String baseUrl,
final String historyUrl) throws Throwable {
runOnUiThread(() -> awContents.loadDataWithBaseURL(baseUrl, data, mimeType,
isBase64Encoded ? "base64" : null, historyUrl));
}
/**
* Reloads the current page synchronously.
*/
public void reloadSync(final AwContents awContents, CallbackHelper onPageFinishedHelper)
throws Exception {
int currentCallCount = onPageFinishedHelper.getCallCount();
TestThreadUtils.runOnUiThreadBlocking(
() -> awContents.getNavigationController().reload(true));
onPageFinishedHelper.waitForCallback(
currentCallCount, 1, WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
/**
* Stops loading on the UI thread.
*/
public void stopLoading(final AwContents awContents) {
TestThreadUtils.runOnUiThreadBlocking(() -> awContents.stopLoading());
}
public void waitForVisualStateCallback(final AwContents awContents) throws Exception {
final CallbackHelper ch = new CallbackHelper();
final int chCount = ch.getCallCount();
TestThreadUtils.runOnUiThreadBlocking(() -> {
final long requestId = 666;
awContents.insertVisualStateCallback(requestId, new AwContents.VisualStateCallback() {
@Override
public void onComplete(long id) {
Assert.assertEquals(requestId, id);
ch.notifyCalled();
}
});
});
ch.waitForCallback(chCount);
}
public void insertVisualStateCallbackOnUIThread(final AwContents awContents,
final long requestId, final AwContents.VisualStateCallback callback) {
TestThreadUtils.runOnUiThreadBlocking(
() -> awContents.insertVisualStateCallback(requestId, callback));
}
// Waits for the pixel at the center of AwContents to color up into expectedColor.
// Note that this is a stricter condition that waiting for a visual state callback,
// as visual state callback only indicates that *something* has appeared in WebView.
public void waitForPixelColorAtCenterOfView(final AwContents awContents,
final AwTestContainerView testContainerView, final int expectedColor) {
pollUiThread(() -> GraphicsTestUtils.getPixelColorAtCenterOfView(
awContents, testContainerView) == expectedColor);
}
public AwTestContainerView createAwTestContainerView(final AwContentsClient awContentsClient) {
return createAwTestContainerView(awContentsClient, false, null);
}
public AwTestContainerView createAwTestContainerView(final AwContentsClient awContentsClient,
boolean supportsLegacyQuirks, final TestDependencyFactory testDependencyFactory) {
AwTestContainerView testContainerView = createDetachedAwTestContainerView(
awContentsClient, supportsLegacyQuirks, testDependencyFactory);
getActivity().addView(testContainerView);
testContainerView.requestFocus();
return testContainerView;
}
public AwBrowserContext getAwBrowserContext() {
return mBrowserContext;
}
public AwTestContainerView createDetachedAwTestContainerView(
final AwContentsClient awContentsClient) {
return createDetachedAwTestContainerView(awContentsClient, false, null);
}
public AwTestContainerView createDetachedAwTestContainerView(
final AwContentsClient awContentsClient, boolean supportsLegacyQuirks,
TestDependencyFactory testDependencyFactory) {
if (testDependencyFactory == null) {
testDependencyFactory = createTestDependencyFactory();
}
boolean allowHardwareAcceleration = isHardwareAcceleratedTest();
final AwTestContainerView testContainerView =
testDependencyFactory.createAwTestContainerView(
getActivity(), allowHardwareAcceleration);
AwSettings awSettings =
testDependencyFactory.createAwSettings(getActivity(), supportsLegacyQuirks);
AwContents awContents = testDependencyFactory.createAwContents(mBrowserContext,
testContainerView, testContainerView.getContext(),
testContainerView.getInternalAccessDelegate(),
testContainerView.getNativeDrawFunctorFactory(), awContentsClient, awSettings,
testDependencyFactory);
testContainerView.initialize(awContents);
mAwContentsDestroyedInTearDown.add(new WeakReference<>(awContents));
return testContainerView;
}
public boolean isHardwareAcceleratedTest() {
return !testMethodHasAnnotation(DisableHardwareAccelerationForTest.class);
}
public AwTestContainerView createAwTestContainerViewOnMainSync(final AwContentsClient client) {
return createAwTestContainerViewOnMainSync(client, false, null);
}
public AwTestContainerView createAwTestContainerViewOnMainSync(
final AwContentsClient client, final boolean supportsLegacyQuirks) {
return createAwTestContainerViewOnMainSync(client, supportsLegacyQuirks, null);
}
public AwTestContainerView createAwTestContainerViewOnMainSync(final AwContentsClient client,
final boolean supportsLegacyQuirks, final TestDependencyFactory testDependencyFactory) {
return TestThreadUtils.runOnUiThreadBlockingNoException(
() -> createAwTestContainerView(
client, supportsLegacyQuirks, testDependencyFactory));
}
public void destroyAwContentsOnMainSync(final AwContents awContents) {
if (awContents == null) return;
TestThreadUtils.runOnUiThreadBlocking(() -> awContents.destroy());
}
public String getTitleOnUiThread(final AwContents awContents) throws Exception {
return TestThreadUtils.runOnUiThreadBlocking(() -> awContents.getTitle());
}
public AwSettings getAwSettingsOnUiThread(final AwContents awContents) throws Exception {
return TestThreadUtils.runOnUiThreadBlocking(() -> awContents.getSettings());
}
/**
* Verify double quotes in both sides of the raw string. Strip the double quotes and
* returns rest of the string.
*/
public String maybeStripDoubleQuotes(String raw) {
Assert.assertNotNull(raw);
Matcher m = MAYBE_QUOTED_STRING.matcher(raw);
Assert.assertTrue(m.matches());
return m.group(2);
}
/**
* Executes the given snippet of JavaScript code within the given ContentView. Returns the
* result of its execution in JSON format.
*/
public String executeJavaScriptAndWaitForResult(final AwContents awContents,
TestAwContentsClient viewClient, final String code) throws Exception {
return JSUtils.executeJavaScriptAndWaitForResult(
InstrumentationRegistry.getInstrumentation(), awContents,
viewClient.getOnEvaluateJavaScriptResultHelper(), code);
}
/**
* Executes JavaScript code within the given ContentView to get the text content in
* document body. Returns the result string without double quotes.
*/
public String getJavaScriptResultBodyTextContent(
final AwContents awContents, final TestAwContentsClient viewClient) throws Exception {
String raw = executeJavaScriptAndWaitForResult(
awContents, viewClient, "document.body.textContent");
return maybeStripDoubleQuotes(raw);
}
/**
* Adds a JavaScript interface to the AwContents. Does its work synchronously on the UI thread,
* and can be called from any thread. All the rules of {@link
* android.webkit.WebView#addJavascriptInterface} apply to this method (ex. you must call this
* <b>prior</b> to loading the frame you intend to load the JavaScript interface into).
*
* @param awContents the AwContents in which to insert the JavaScript interface.
* @param objectToInject the JavaScript interface to inject.
* @param javascriptIdentifier the name with which to refer to {@code objectToInject} from
* JavaScript code.
*/
public static void addJavascriptInterfaceOnUiThread(final AwContents awContents,
final Object objectToInject, final String javascriptIdentifier) {
TestThreadUtils.runOnUiThreadBlocking(
() -> awContents.addJavascriptInterface(objectToInject, javascriptIdentifier));
}
/**
* Wrapper around CriteriaHelper.pollInstrumentationThread. This uses AwActivityTestRule-specifc
* timeouts and treats timeouts and exceptions as test failures automatically.
*/
public static void pollInstrumentationThread(final Callable<Boolean> callable) {
CriteriaHelper.pollInstrumentationThread(() -> {
try {
return callable.call();
} catch (Throwable e) {
Log.e(TAG, "Exception while polling.", e);
return false;
}
}, WAIT_TIMEOUT_MS, CHECK_INTERVAL);
}
/**
* Wrapper around {@link AwActivityTestRule#pollInstrumentationThread()} but runs the
* callable on the UI thread.
*/
public void pollUiThread(final Callable<Boolean> callable) {
pollInstrumentationThread(() -> TestThreadUtils.runOnUiThreadBlocking(callable));
}
/**
* Waits for {@code future} and returns its value (or times out). If {@code future} has an
* associated Exception, this will re-throw that Exception on the instrumentation thread
* (wrapping with an unchecked Exception if necessary, to avoid requiring callers to declare
* checked Exceptions).
*
* @param future the {@link Future} representing a value of interest.
* @return the value {@code future} represents.
*/
public static <T> T waitForFuture(Future<T> future) {
try {
return future.get(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
} catch (ExecutionException e) {
// ExecutionException means this Future has an associated Exception that we should
// re-throw on the current thread. We throw the cause instead of ExecutionException,
// since ExecutionException itself isn't interesting, and might mislead those debugging
// test failures to suspect this method is the culprit (whereas the root cause is from
// another thread).
Throwable cause = e.getCause();
// If the cause is an unchecked Throwable type, re-throw as-is.
if (cause instanceof Error) throw(Error) cause;
if (cause instanceof RuntimeException) throw(RuntimeException) cause;
// Otherwise, wrap this in an unchecked Exception so callers don't need to declare
// checked Exceptions.
throw new RuntimeException(cause);
} catch (InterruptedException | TimeoutException e) {
// Don't call e.getCause() for either of these. Unlike ExecutionException, these don't
// wrap the root cause, but rather are themselves interesting. Again, we wrap these
// checked Exceptions with an unchecked Exception for the caller's convenience.
//
// Although we might be tempted to handle InterruptedException by calling
// Thread.currentThread().interrupt(), this is not correct in this case. The interrupted
// thread was likely a different thread than the current thread, so there's nothing
// special we need to do.
throw new RuntimeException(e);
}
}
/**
* Takes an element out of the {@link BlockingQueue} (or times out).
*/
public static <T> T waitForNextQueueElement(BlockingQueue<T> queue) throws Exception {
T value = queue.poll(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
if (value == null) {
// {@code null} is the special value which means {@link BlockingQueue#poll} has timed
// out (also: there's no risk for collision with real values, because BlockingQueue does
// not allow null entries). Instead of returning this special value, let's throw a
// proper TimeoutException.
throw new TimeoutException(
"Timeout while trying to take next entry from BlockingQueue");
}
return value;
}
/**
* Clears the resource cache. Note that the cache is per-application, so this will clear the
* cache for all WebViews used.
*/
public void clearCacheOnUiThread(final AwContents awContents, final boolean includeDiskFiles) {
TestThreadUtils.runOnUiThreadBlocking(() -> awContents.clearCache(includeDiskFiles));
}
/**
* Returns pure page scale.
*/
public float getScaleOnUiThread(final AwContents awContents) throws Exception {
return TestThreadUtils.runOnUiThreadBlocking(() -> awContents.getPageScaleFactor());
}
/**
* Returns page scale multiplied by the screen density.
*/
public float getPixelScaleOnUiThread(final AwContents awContents) throws Exception {
return TestThreadUtils.runOnUiThreadBlocking(() -> awContents.getScale());
}
/**
* Returns whether a user can zoom the page in.
*/
public boolean canZoomInOnUiThread(final AwContents awContents) throws Exception {
return TestThreadUtils.runOnUiThreadBlocking(() -> awContents.canZoomIn());
}
/**
* Returns whether a user can zoom the page out.
*/
public boolean canZoomOutOnUiThread(final AwContents awContents) throws Exception {
return TestThreadUtils.runOnUiThreadBlocking(() -> awContents.canZoomOut());
}
/**
* Loads the main html then triggers the popup window.
*/
public void triggerPopup(final AwContents parentAwContents,
TestAwContentsClient parentAwContentsClient, TestWebServer testWebServer,
String mainHtml, String popupHtml, String popupPath, String triggerScript)
throws Exception {
enableJavaScriptOnUiThread(parentAwContents);
TestThreadUtils.runOnUiThreadBlocking(() -> {
parentAwContents.getSettings().setSupportMultipleWindows(true);
parentAwContents.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
});
final String parentUrl = testWebServer.setResponse("/popupParent.html", mainHtml, null);
if (popupHtml != null) {
testWebServer.setResponse(popupPath, popupHtml, null);
} else {
testWebServer.setResponseWithNoContentStatus(popupPath);
}
parentAwContentsClient.getOnCreateWindowHelper().setReturnValue(true);
loadUrlSync(parentAwContents, parentAwContentsClient.getOnPageFinishedHelper(), parentUrl);
TestAwContentsClient.OnCreateWindowHelper onCreateWindowHelper =
parentAwContentsClient.getOnCreateWindowHelper();
int currentCallCount = onCreateWindowHelper.getCallCount();
TestThreadUtils.runOnUiThreadBlocking(
() -> parentAwContents.evaluateJavaScriptForTests(triggerScript, null));
onCreateWindowHelper.waitForCallback(
currentCallCount, 1, WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
/**
* Supplies the popup window with AwContents then waits for the popup window to finish loading.
* @param parentAwContents Parent webview's AwContents.
*/
public PopupInfo connectPendingPopup(AwContents parentAwContents) throws Exception {
PopupInfo popupInfo = createPopupContents(parentAwContents);
loadPopupContents(parentAwContents, popupInfo, null);
return popupInfo;
}
/**
* Creates a popup window with AwContents.
*/
public PopupInfo createPopupContents(final AwContents parentAwContents) {
TestAwContentsClient popupContentsClient;
AwTestContainerView popupContainerView;
final AwContents popupContents;
popupContentsClient = new TestAwContentsClient();
popupContainerView = createAwTestContainerViewOnMainSync(popupContentsClient);
popupContents = popupContainerView.getAwContents();
enableJavaScriptOnUiThread(popupContents);
return new PopupInfo(popupContentsClient, popupContainerView, popupContents);
}
/**
* Waits for the popup window to finish loading.
* @param parentAwContents Parent webview's AwContents.
* @param info The PopupInfo.
* @param onCreateWindowHandler An instance of OnCreateWindowHandler. null if there isn't.
*/
public void loadPopupContents(final AwContents parentAwContents, PopupInfo info,
OnCreateWindowHandler onCreateWindowHandler) throws Exception {
TestAwContentsClient popupContentsClient = info.popupContentsClient;
AwTestContainerView popupContainerView = info.popupContainerView;
final AwContents popupContents = info.popupContents;
OnPageFinishedHelper onPageFinishedHelper = popupContentsClient.getOnPageFinishedHelper();
int finishCallCount = onPageFinishedHelper.getCallCount();
if (onCreateWindowHandler != null) onCreateWindowHandler.onCreateWindow(popupContents);
TestAwContentsClient.OnReceivedTitleHelper onReceivedTitleHelper =
popupContentsClient.getOnReceivedTitleHelper();
int titleCallCount = onReceivedTitleHelper.getCallCount();
TestThreadUtils.runOnUiThreadBlocking(
() -> parentAwContents.supplyContentsForPopup(popupContents));
onPageFinishedHelper.waitForCallback(
finishCallCount, 1, WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
onReceivedTitleHelper.waitForCallback(
titleCallCount, 1, WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
private boolean testMethodHasAnnotation(Class<? extends Annotation> clazz) {
return mCurrentTestDescription.getAnnotation(clazz) != null ? true : false;
}
/**
* Factory class used in creation of test AwContents instances. Test cases
* can provide subclass instances to the createAwTest* methods in order to
* create an AwContents instance with injected test dependencies.
*/
public static class TestDependencyFactory extends AwContents.DependencyFactory {
public AwTestContainerView createAwTestContainerView(
AwTestRunnerActivity activity, boolean allowHardwareAcceleration) {
return new AwTestContainerView(activity, allowHardwareAcceleration);
}
public AwSettings createAwSettings(Context context, boolean supportsLegacyQuirks) {
return new AwSettings(context, false /* isAccessFromFileURLsGrantedByDefault */,
supportsLegacyQuirks, false /* allowEmptyDocumentPersistence */,
true /* allowGeolocationOnInsecureOrigins */,
false /* doNotUpdateSelectionOnMutatingSelectionRange */);
}
public AwContents createAwContents(AwBrowserContext browserContext, ViewGroup containerView,
Context context, InternalAccessDelegate internalAccessAdapter,
NativeDrawFunctorFactory nativeDrawFunctorFactory, AwContentsClient contentsClient,
AwSettings settings, DependencyFactory dependencyFactory) {
return new AwContents(browserContext, containerView, context, internalAccessAdapter,
nativeDrawFunctorFactory, contentsClient, settings, dependencyFactory);
}
}
/**
* POD object for holding references to helper objects of a popup window.
*/
public static class PopupInfo {
public final TestAwContentsClient popupContentsClient;
public final AwTestContainerView popupContainerView;
public final AwContents popupContents;
public PopupInfo(TestAwContentsClient popupContentsClient,
AwTestContainerView popupContainerView, AwContents popupContents) {
this.popupContentsClient = popupContentsClient;
this.popupContainerView = popupContainerView;
this.popupContents = popupContents;
}
}
}