[go: nahoru, domu]

blob: 3956427fedb98e5c2e98b6e41971f2bd47d90928 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.android_webview.test;
import static org.chromium.android_webview.test.OnlyRunIn.ProcessMode.MULTI_PROCESS;
import android.os.SystemClock;
import android.view.KeyEvent;
import android.webkit.JavascriptInterface;
import androidx.test.filters.LargeTest;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.UseParametersRunnerFactory;
import org.chromium.android_webview.AwContents;
import org.chromium.android_webview.AwRenderProcess;
import org.chromium.android_webview.AwRenderProcessGoneDetail;
import org.chromium.base.task.PostTask;
import org.chromium.base.task.TaskTraits;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.Feature;
import org.chromium.content_public.common.ContentUrlConstants;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/** Tests for AwContentsClient.onRenderProcessGone callback. */
@RunWith(Parameterized.class)
@UseParametersRunnerFactory(AwJUnit4ClassRunnerWithParameters.Factory.class)
public class AwContentsClientOnRendererUnresponsiveTest extends AwParameterizedTest {
@Rule public AwActivityTestRule mActivityTestRule;
private static final String TAG = "AwRendererUnresponsive";
private static class JSBlocker {
// The Blink thread waits on this in block(), until the test thread calls releaseBlock().
private CountDownLatch mBlockingLatch;
// The test thread waits on this in waitUntilBlocked(),
// until the Blink thread calls block().
private CountDownLatch mThreadWasBlockedLatch;
JSBlocker() {
mBlockingLatch = new CountDownLatch(1);
mThreadWasBlockedLatch = new CountDownLatch(1);
}
public void releaseBlock() {
mBlockingLatch.countDown();
}
@JavascriptInterface
public void block() throws Exception {
mThreadWasBlockedLatch.countDown();
mBlockingLatch.await(AwActivityTestRule.SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
public void waitUntilBlocked() throws Exception {
mThreadWasBlockedLatch.await(
AwActivityTestRule.SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
}
}
private static class RendererTransientlyUnresponsiveTestAwContentsClient
extends TestAwContentsClient {
private CallbackHelper mUnresponsiveCallbackHelper;
private CallbackHelper mResponsiveCallbackHelper;
private JSBlocker mBlocker;
public RendererTransientlyUnresponsiveTestAwContentsClient() {
mUnresponsiveCallbackHelper = new CallbackHelper();
mResponsiveCallbackHelper = new CallbackHelper();
mBlocker = new JSBlocker();
}
void transientlyBlockBlinkThread(final AwContents awContents) throws Exception {
PostTask.runOrPostTask(
TaskTraits.UI_DEFAULT,
() -> {
awContents.evaluateJavaScript("blocker.block();", null);
});
mBlocker.waitUntilBlocked();
}
void awaitRecovery() throws Exception {
mUnresponsiveCallbackHelper.waitForCallback(
0, 1, AwActivityTestRule.WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
Assert.assertEquals(1, mUnresponsiveCallbackHelper.getCallCount());
mResponsiveCallbackHelper.waitForCallback(
0, 1, AwActivityTestRule.WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
Assert.assertEquals(1, mResponsiveCallbackHelper.getCallCount());
}
JSBlocker getBlocker() {
return mBlocker;
}
@Override
public void onRendererResponsive(AwRenderProcess process) {
mResponsiveCallbackHelper.notifyCalled();
}
@Override
public void onRendererUnresponsive(AwRenderProcess process) {
// onRendererResponsive should not have been called yet.
Assert.assertEquals(0, mResponsiveCallbackHelper.getCallCount());
mUnresponsiveCallbackHelper.notifyCalled();
mBlocker.releaseBlock();
}
}
private static class RendererUnresponsiveTestAwContentsClient extends TestAwContentsClient {
// The renderer unresponsive callback should be called repeatedly. We will wait for two
// callbacks.
static final int UNRESPONSIVE_CALLBACK_COUNT = 2;
private CallbackHelper mUnresponsiveCallbackHelper;
private CallbackHelper mTerminatedCallbackHelper;
private JSBlocker mBlocker;
public RendererUnresponsiveTestAwContentsClient() {
mUnresponsiveCallbackHelper = new CallbackHelper();
mTerminatedCallbackHelper = new CallbackHelper();
mBlocker = new JSBlocker();
}
void permanentlyBlockBlinkThread(final AwContents awContents) throws Exception {
PostTask.runOrPostTask(
TaskTraits.UI_DEFAULT,
() -> {
awContents.evaluateJavaScript("blocker.block();", null);
});
mBlocker.waitUntilBlocked();
}
void awaitRendererTermination() throws Exception {
mUnresponsiveCallbackHelper.waitForCallback(
0,
UNRESPONSIVE_CALLBACK_COUNT,
AwActivityTestRule.WAIT_TIMEOUT_MS,
TimeUnit.MILLISECONDS);
Assert.assertEquals(
UNRESPONSIVE_CALLBACK_COUNT, mUnresponsiveCallbackHelper.getCallCount());
mTerminatedCallbackHelper.waitForCallback(
0, 1, AwActivityTestRule.WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
Assert.assertEquals(1, mTerminatedCallbackHelper.getCallCount());
}
JSBlocker getBlocker() {
return mBlocker;
}
@Override
public boolean onRenderProcessGone(AwRenderProcessGoneDetail detail) {
mTerminatedCallbackHelper.notifyCalled();
return true;
}
@Override
public void onRendererUnresponsive(AwRenderProcess process) {
mUnresponsiveCallbackHelper.notifyCalled();
if (mUnresponsiveCallbackHelper.getCallCount() == UNRESPONSIVE_CALLBACK_COUNT) {
process.terminate();
}
}
}
public AwContentsClientOnRendererUnresponsiveTest(AwSettingsMutation param) {
this.mActivityTestRule = new AwActivityTestRule(param.getMutation());
}
private void sendInputEvent(final AwContents awContents) {
PostTask.runOrPostTask(
TaskTraits.UI_DEFAULT,
() -> {
long eventTime = SystemClock.uptimeMillis();
awContents.dispatchKeyEvent(
new KeyEvent(
eventTime,
eventTime,
KeyEvent.ACTION_DOWN,
KeyEvent.KEYCODE_ENTER,
0));
});
}
private void addJsBlockerInterface(final AwContents awContents, final JSBlocker blocker)
throws Exception {
AwActivityTestRule.addJavascriptInterfaceOnUiThread(awContents, blocker, "blocker");
}
// This test requires the ability to terminate the renderer in order to recover from a
// permanently stuck blink main thread, so it can only run in multiprocess.
@Test
@Feature({"AndroidWebView"})
@LargeTest
@OnlyRunIn(MULTI_PROCESS)
public void testOnRendererUnresponsive() throws Throwable {
RendererUnresponsiveTestAwContentsClient contentsClient =
new RendererUnresponsiveTestAwContentsClient();
AwTestContainerView testView =
mActivityTestRule.createAwTestContainerViewOnMainSync(contentsClient);
final AwContents awContents = testView.getAwContents();
AwActivityTestRule.enableJavaScriptOnUiThread(awContents);
addJsBlockerInterface(awContents, contentsClient.getBlocker());
mActivityTestRule.loadUrlSync(
awContents,
contentsClient.getOnPageFinishedHelper(),
ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL);
contentsClient.permanentlyBlockBlinkThread(awContents);
// Sending a key event while the renderer is unresponsive will cause onRendererUnresponsive
// to be called.
sendInputEvent(awContents);
contentsClient.awaitRendererTermination();
}
@Test
@Feature({"AndroidWebView"})
@LargeTest
public void testTransientUnresponsiveness() throws Throwable {
RendererTransientlyUnresponsiveTestAwContentsClient contentsClient =
new RendererTransientlyUnresponsiveTestAwContentsClient();
AwTestContainerView testView =
mActivityTestRule.createAwTestContainerViewOnMainSync(contentsClient);
final AwContents awContents = testView.getAwContents();
AwActivityTestRule.enableJavaScriptOnUiThread(awContents);
addJsBlockerInterface(awContents, contentsClient.getBlocker());
mActivityTestRule.loadUrlSync(
awContents,
contentsClient.getOnPageFinishedHelper(),
ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL);
contentsClient.transientlyBlockBlinkThread(awContents);
sendInputEvent(awContents);
contentsClient.awaitRecovery();
}
}