[go: nahoru, domu]

blob: 119730bf56d4f57d674383a32e54c43ca6b8ab7d [file] [log] [blame]
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.android_webview.test;
import static org.chromium.android_webview.test.AwActivityTestRule.WAIT_TIMEOUT_MS;
import static org.chromium.base.test.util.ScalableTimeout.scaleTimeout;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Rect;
import android.util.Base64;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
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.AwContents.VisualStateCallback;
import org.chromium.android_webview.AwContentsClient;
import org.chromium.android_webview.test.util.CommonResources;
import org.chromium.android_webview.test.util.GraphicsTestUtils;
import org.chromium.android_webview.test.util.JSUtils;
import org.chromium.android_webview.test.util.JavascriptEventObserver;
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.components.embedder_support.util.WebResourceResponseInfo;
import org.chromium.content_public.browser.JavascriptInjector;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import java.io.ByteArrayInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/** Visual state related tests. */
@RunWith(Parameterized.class)
@UseParametersRunnerFactory(AwJUnit4ClassRunnerWithParameters.Factory.class)
public class VisualStateTest extends AwParameterizedTest {
@Rule public AwActivityTestRule mActivityTestRule;
private static final String WAIT_FOR_JS_TEST_URL =
"file:///android_asset/visual_state_waits_for_js_test.html";
private static final String WAIT_FOR_JS_DETACHED_TEST_URL =
"file:///android_asset/visual_state_waits_for_js_detached_test.html";
private static final String ON_PAGE_COMMIT_VISIBLE_TEST_URL =
"file:///android_asset/visual_state_on_page_commit_visible_test.html";
private static final String FULLSCREEN_TEST_URL =
"file:///android_asset/visual_state_during_fullscreen_test.html";
private static final String UPDATE_COLOR_CONTROL_ID = "updateColorControl";
private static final String ENTER_FULLSCREEN_CONTROL_ID = "enterFullscreenControl";
private TestAwContentsClient mContentsClient = new TestAwContentsClient();
private AwTestContainerView mTestView;
private static class DelayedInputStream extends FilterInputStream {
private CountDownLatch mLatch = new CountDownLatch(1);
DelayedInputStream(InputStream in) {
super(in);
}
@Override
@SuppressWarnings("Finally")
public int read() throws IOException {
try {
mLatch.await();
} finally {
return super.read();
}
}
@Override
@SuppressWarnings("Finally")
public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
try {
mLatch.await();
} finally {
return super.read(buffer, byteOffset, byteCount);
}
}
public void allowReads() {
mLatch.countDown();
}
}
private static class SlowBlueImage extends WebResourceResponseInfo {
// This image delays returning data for 1 (scaled) second in order to simlate a slow network
// connection.
public static final long IMAGE_LOADING_DELAY_MS = scaleTimeout(1000);
public SlowBlueImage() {
super(
"image/png",
"utf-8",
new DelayedInputStream(
new ByteArrayInputStream(
Base64.decode(
CommonResources.BLUE_PNG_BASE64, Base64.DEFAULT))));
}
@Override
public InputStream getData() {
final DelayedInputStream stream = (DelayedInputStream) super.getData();
PostTask.postDelayedTask(
TaskTraits.UI_DEFAULT, () -> stream.allowReads(), IMAGE_LOADING_DELAY_MS);
return stream;
}
}
public VisualStateTest(AwSettingsMutation param) {
this.mActivityTestRule = new AwActivityTestRule(param.getMutation());
}
@Test
@Feature({"AndroidWebView"})
@SmallTest
public void testVisualStateCallbackIsReceived() throws Throwable {
mTestView = mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
final AwContents awContents = mTestView.getAwContents();
mActivityTestRule.loadDataSync(
awContents,
mContentsClient.getOnPageFinishedHelper(),
CommonResources.ABOUT_HTML,
"text/html",
false);
final CallbackHelper ch = new CallbackHelper();
final int chCount = ch.getCallCount();
InstrumentationRegistry.getInstrumentation()
.runOnMainSync(
() -> {
final long requestId =
0x123456789abcdef0L; // ensure requestId is not truncated.
awContents.insertVisualStateCallback(
requestId,
new VisualStateCallback() {
@Override
public void onComplete(long id) {
Assert.assertEquals(requestId, id);
ch.notifyCalled();
}
});
});
ch.waitForCallback(chCount);
}
@Test
@Feature({"AndroidWebView"})
@SmallTest
public void testVisualStateCallbackWaitsForContentsToBeOnScreen() throws Throwable {
// This test loads a page with a blue background color. It then waits for the DOM tree
// in blink to contain the contents of the blue page (which happens when the onPageFinished
// event is received). It then flushes the contents and verifies that the blue page
// background color is drawn when the flush callback is received.
final LoadUrlParams bluePageUrl = createTestPageUrl("blue");
final CountDownLatch testFinishedSignal = new CountDownLatch(1);
final AtomicReference<AwContents> awContentsRef = new AtomicReference<>();
final long requestId = 10;
final var visualStateCallback =
new VisualStateCallback() {
@Override
public void onComplete(long id) {
Assert.assertEquals(requestId, id);
Bitmap blueScreenshot =
GraphicsTestUtils.drawAwContents(awContentsRef.get(), 1, 1);
Assert.assertEquals(Color.BLUE, blueScreenshot.getPixel(0, 0));
testFinishedSignal.countDown();
}
};
mTestView =
mActivityTestRule.createAwTestContainerViewOnMainSync(
new TestAwContentsClient() {
@Override
public void onPageFinished(String url) {
if (bluePageUrl.getUrl().equals(url)) {
awContentsRef
.get()
.insertVisualStateCallback(
requestId, visualStateCallback);
}
}
});
final AwContents awContents = mTestView.getAwContents();
awContentsRef.set(awContents);
InstrumentationRegistry.getInstrumentation()
.runOnMainSync(
() -> {
awContents.setBackgroundColor(Color.RED);
awContents.loadUrl(bluePageUrl);
// We have just loaded the blue page, but the graphics pipeline is
// asynchronous so at this point the WebView still draws red, ie. the
// View background color.
// Only when the flush callback is received will we know for certain
// that the blue page contents are on screen.
Bitmap redScreenshot =
GraphicsTestUtils.drawAwContents(awContentsRef.get(), 1, 1);
Assert.assertEquals(Color.RED, redScreenshot.getPixel(0, 0));
});
Assert.assertTrue(
testFinishedSignal.await(
AwActivityTestRule.SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
@Test
@Feature({"AndroidWebView"})
@SmallTest
@SkipMutations(reason = "This test depends on AwSettings.setImagesEnabled(true)")
public void testOnPageCommitVisible() throws Throwable {
// This test loads a page with a blue background color. It then waits for the DOM tree
// in blink to contain the contents of the blue page (which happens when the onPageFinished
// event is received). It then flushes the contents and verifies that the blue page
// background color is drawn when the flush callback is received.
final CountDownLatch testFinishedSignal = new CountDownLatch(1);
final CountDownLatch pageCommitCallbackOccurred = new CountDownLatch(1);
final AtomicReference<AwContents> awContentsRef = new AtomicReference<>();
final var visualStateCallback =
new VisualStateCallback() {
@Override
public void onComplete(long id) {
Bitmap bitmap =
GraphicsTestUtils.drawAwContents(awContentsRef.get(), 256, 256);
Assert.assertEquals(Color.BLUE, bitmap.getPixel(128, 128));
testFinishedSignal.countDown();
}
};
mTestView =
mActivityTestRule.createAwTestContainerViewOnMainSync(
new TestAwContentsClient() {
@Override
public void onPageCommitVisible(String url) {
Bitmap bitmap =
GraphicsTestUtils.drawAwContents(
awContentsRef.get(), 256, 256);
Assert.assertEquals(Color.GREEN, bitmap.getPixel(128, 128));
pageCommitCallbackOccurred.countDown();
}
@Override
public WebResourceResponseInfo shouldInterceptRequest(
AwWebResourceRequest request) {
if (request.url.equals("intercepted://blue.png")) {
try {
return new SlowBlueImage();
} catch (Throwable t) {
return null;
}
}
return null;
}
@Override
public void onPageFinished(String url) {
super.onPageFinished(url);
awContentsRef
.get()
.insertVisualStateCallback(10, visualStateCallback);
}
});
final AwContents awContents = mTestView.getAwContents();
awContentsRef.set(awContents);
InstrumentationRegistry.getInstrumentation()
.runOnMainSync(
() -> {
awContents.setBackgroundColor(Color.RED);
awContents.loadUrl(new LoadUrlParams(ON_PAGE_COMMIT_VISIBLE_TEST_URL));
// We have just loaded the blue page, but the graphics pipeline is
// asynchronous so at this point the WebView still draws red, ie. the
// View background color.
// Only when the flush callback is received will we know for certain
// that the blue page contents are on screen.
Bitmap bitmap =
GraphicsTestUtils.drawAwContents(awContentsRef.get(), 256, 256);
Assert.assertEquals(Color.RED, bitmap.getPixel(128, 128));
});
Assert.assertTrue(
pageCommitCallbackOccurred.await(
AwActivityTestRule.SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
Assert.assertTrue(
testFinishedSignal.await(
AwActivityTestRule.SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
@Test
@Feature({"AndroidWebView"})
@SmallTest
public void testVisualStateCallbackWaitsForJs() throws Throwable {
// This test checks that when a VisualStateCallback completes the results of executing
// any block of JS prior to the time at which the callback was inserted will be visible
// in the next draw. For that it loads a page that changes the background color of
// the page from JS when a button is clicked.
final CountDownLatch readyToUpdateColor = new CountDownLatch(1);
final CountDownLatch testFinishedSignal = new CountDownLatch(1);
final AtomicReference<AwContents> awContentsRef = new AtomicReference<>();
final var visualStateCallback =
new VisualStateCallback() {
@Override
public void onComplete(long id) {
Bitmap blueScreenshot =
GraphicsTestUtils.drawAwContents(awContentsRef.get(), 100, 100);
Assert.assertEquals(Color.BLUE, blueScreenshot.getPixel(50, 50));
readyToUpdateColor.countDown();
}
};
TestAwContentsClient awContentsClient =
new TestAwContentsClient() {
@Override
public void onPageFinished(String url) {
super.onPageFinished(url);
awContentsRef.get().insertVisualStateCallback(10, visualStateCallback);
}
};
mTestView = mActivityTestRule.createAwTestContainerViewOnMainSync(awContentsClient);
final AwContents awContents = mTestView.getAwContents();
awContentsRef.set(awContents);
final WebContents webContents = mTestView.getWebContents();
AwActivityTestRule.enableJavaScriptOnUiThread(awContents);
// JS will notify this observer once it has changed the background color of the page.
final JavascriptEventObserver jsObserver = new JavascriptEventObserver();
InstrumentationRegistry.getInstrumentation()
.runOnMainSync(
() -> jsObserver.register(awContents.getWebContents(), "jsObserver"));
mActivityTestRule.loadUrlSync(
awContents, awContentsClient.getOnPageFinishedHelper(), WAIT_FOR_JS_TEST_URL);
Assert.assertTrue(
readyToUpdateColor.await(
AwActivityTestRule.SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
JSUtils.clickNodeWithUserGesture(webContents, UPDATE_COLOR_CONTROL_ID);
Assert.assertTrue(jsObserver.waitForEvent(WAIT_TIMEOUT_MS));
final var visualStateCallback2 =
new VisualStateCallback() {
@Override
public void onComplete(long id) {
Bitmap redScreenshot =
GraphicsTestUtils.drawAwContents(awContents, 100, 100);
Assert.assertEquals(Color.RED, redScreenshot.getPixel(50, 50));
testFinishedSignal.countDown();
}
};
InstrumentationRegistry.getInstrumentation()
.runOnMainSync(
() -> awContents.insertVisualStateCallback(20, visualStateCallback2));
Assert.assertTrue(
testFinishedSignal.await(
AwActivityTestRule.SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
@Test
@Feature({"AndroidWebView"})
@SmallTest
public void testVisualStateCallbackFromJsDuringFullscreenTransitions() throws Throwable {
// This test checks that VisualStateCallbacks are delivered correctly during
// fullscreen transitions. It loads a page, clicks a button to enter fullscreen,
// then inserts a VisualStateCallback once notified from JS and verifies that when the
// callback is received the fullscreen contents are rendered correctly in the next draw.
final CountDownLatch readyToEnterFullscreenSignal = new CountDownLatch(1);
final CountDownLatch testFinishedSignal = new CountDownLatch(1);
final AtomicReference<AwContents> awContentsRef = new AtomicReference<>();
final var visualStateCallback =
new VisualStateCallback() {
@Override
public void onComplete(long id) {
Bitmap blueScreenshot =
GraphicsTestUtils.drawAwContents(awContentsRef.get(), 100, 100);
Assert.assertEquals(Color.BLUE, blueScreenshot.getPixel(50, 50));
readyToEnterFullscreenSignal.countDown();
}
};
final FullScreenVideoTestAwContentsClient awContentsClient =
new FullScreenVideoTestAwContentsClient(
mActivityTestRule.getActivity(),
mActivityTestRule.isHardwareAcceleratedTest()) {
@Override
public void onPageFinished(String url) {
super.onPageFinished(url);
awContentsRef.get().insertVisualStateCallback(10, visualStateCallback);
}
};
mTestView = mActivityTestRule.createAwTestContainerViewOnMainSync(awContentsClient);
final AwContents awContents = mTestView.getAwContents();
awContentsRef.set(awContents);
final WebContents webContents = mTestView.getWebContents();
AwActivityTestRule.enableJavaScriptOnUiThread(awContents);
awContents.getSettings().setFullscreenSupported(true);
// JS will notify this observer once it has entered fullscreen.
final JavascriptEventObserver jsObserver = new JavascriptEventObserver();
InstrumentationRegistry.getInstrumentation()
.runOnMainSync(
() -> jsObserver.register(awContents.getWebContents(), "jsObserver"));
mActivityTestRule.loadUrlSync(
awContents, awContentsClient.getOnPageFinishedHelper(), FULLSCREEN_TEST_URL);
Assert.assertTrue(
readyToEnterFullscreenSignal.await(
AwActivityTestRule.SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
JSUtils.clickNodeWithUserGesture(webContents, ENTER_FULLSCREEN_CONTROL_ID);
Assert.assertTrue(jsObserver.waitForEvent(WAIT_TIMEOUT_MS));
final var visualStateCallback2 =
new VisualStateCallback() {
@Override
public void onComplete(long id) {
// NOTE: We cannot use drawAwContents here because
// the web contents are rendered into the custom
// view while in fullscreen.
Bitmap redScreenshot =
GraphicsTestUtils.drawView(
awContentsClient.getCustomView(), 100, 100);
Assert.assertEquals(Color.RED, redScreenshot.getPixel(50, 50));
testFinishedSignal.countDown();
}
};
InstrumentationRegistry.getInstrumentation()
.runOnMainSync(
() -> awContents.insertVisualStateCallback(20, visualStateCallback2));
Assert.assertTrue(
testFinishedSignal.await(
AwActivityTestRule.SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
private AwTestContainerView createDetachedTestContainerViewOnMainSync(
final AwContentsClient awContentsClient) {
return TestThreadUtils.runOnUiThreadBlockingNoException(
() -> {
AwTestContainerView detachedView =
mActivityTestRule.createDetachedAwTestContainerView(awContentsClient);
detachedView.setClipBounds(new Rect(0, 0, 100, 100));
detachedView.measure(100, 100);
detachedView.layout(0, 0, 100, 100);
return detachedView;
});
}
@Test
@Feature({"AndroidWebView"})
@SmallTest
public void testVisualStateCallbackWhenContainerViewDetached() throws Throwable {
final CountDownLatch testFinishedSignal = new CountDownLatch(1);
final TestAwContentsClient awContentsClient = new TestAwContentsClient();
mTestView = createDetachedTestContainerViewOnMainSync(awContentsClient);
final AwContents awContents = mTestView.getAwContents();
AwActivityTestRule.enableJavaScriptOnUiThread(awContents);
// JS will notify this observer once it has changed the background color of the page.
final var visualStateCallback =
new VisualStateCallback() {
@Override
public void onComplete(long id) {
Bitmap redScreenshot =
GraphicsTestUtils.drawAwContents(awContents, 100, 100);
Assert.assertEquals(Color.RED, redScreenshot.getPixel(50, 50));
testFinishedSignal.countDown();
}
};
final Object pageChangeNotifier =
new Object() {
public void onPageChanged() {
PostTask.postTask(
TaskTraits.UI_DEFAULT,
() ->
awContents.insertVisualStateCallback(
20, visualStateCallback));
}
};
InstrumentationRegistry.getInstrumentation()
.runOnMainSync(
() -> {
JavascriptInjector.fromWebContents(awContents.getWebContents(), false)
.addPossiblyUnsafeInterface(
pageChangeNotifier, "pageChangeNotifier", null);
awContents.loadUrl(WAIT_FOR_JS_DETACHED_TEST_URL);
});
Assert.assertTrue(
testFinishedSignal.await(
AwActivityTestRule.SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
private static final LoadUrlParams createTestPageUrl(String backgroundColor) {
return LoadUrlParams.createLoadDataParams(
"<html><body bgcolor=" + backgroundColor + "></body></html>", "text/html", false);
}
}