chromium/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsRenderTest.java

// Copyright 2013 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 android.graphics.Bitmap;
import android.graphics.Color;
import android.view.View;

import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;

import org.junit.Assert;
import org.junit.Before;
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.test.util.GraphicsTestUtils;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.UrlUtils;
import org.chromium.content_public.common.ContentUrlConstants;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/** AwContents rendering / pixel tests. */
@RunWith(Parameterized.class)
@UseParametersRunnerFactory(AwJUnit4ClassRunnerWithParameters.Factory.class)
public class AwContentsRenderTest extends AwParameterizedTest {
    @Rule public AwActivityTestRule mActivityTestRule;

    private TestAwContentsClient mContentsClient;
    private AwContents mAwContents;
    private AwTestContainerView mContainerView;

    public AwContentsRenderTest(AwSettingsMutation param) {
        this.mActivityTestRule = new AwActivityTestRule(param.getMutation());
    }

    @Before
    public void setUp() {
        mContentsClient = new TestAwContentsClient();
        mContainerView = mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
        mAwContents = mContainerView.getAwContents();
    }

    void setBackgroundColorOnUiThread(final int c) {
        ThreadUtils.runOnUiThreadBlocking(() -> mAwContents.setBackgroundColor(c));
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView"})
    public void testSetGetBackgroundColor() throws Throwable {
        setBackgroundColorOnUiThread(Color.MAGENTA);
        GraphicsTestUtils.pollForBackgroundColor(mAwContents, Color.MAGENTA);

        setBackgroundColorOnUiThread(Color.CYAN);
        GraphicsTestUtils.pollForBackgroundColor(mAwContents, Color.CYAN);

        mActivityTestRule.loadUrlSync(
                mAwContents,
                mContentsClient.getOnPageFinishedHelper(),
                ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL);
        GraphicsTestUtils.pollForBackgroundColor(mAwContents, Color.CYAN);

        setBackgroundColorOnUiThread(Color.YELLOW);
        GraphicsTestUtils.pollForBackgroundColor(mAwContents, Color.YELLOW);

        final String html_meta = "<html><head><meta name=color-scheme content=dark></head></html>";
        mActivityTestRule.loadUrlSync(
                mAwContents,
                mContentsClient.getOnPageFinishedHelper(),
                "data:text/html," + html_meta);
        final int dark_scheme_color = 0xFF121212;
        GraphicsTestUtils.pollForBackgroundColor(mAwContents, dark_scheme_color);

        final String html =
                "<html><head><style>body {background-color:#227788}</style></head>"
                        + "<body></body></html>";
        // Loading the html via a data URI requires us to encode '#' symbols as '%23'.
        mActivityTestRule.loadUrlSync(
                mAwContents,
                mContentsClient.getOnPageFinishedHelper(),
                "data:text/html," + html.replace("#", "%23"));
        final int teal = 0xFF227788;
        GraphicsTestUtils.pollForBackgroundColor(mAwContents, teal);

        // Changing the base background should not override CSS background.
        setBackgroundColorOnUiThread(Color.MAGENTA);
        Assert.assertEquals(teal, GraphicsTestUtils.sampleBackgroundColorOnUiThread(mAwContents));
        // ...setting the background is asynchronous, so pause a bit and retest just to be sure.
        Thread.sleep(500);
        Assert.assertEquals(teal, GraphicsTestUtils.sampleBackgroundColorOnUiThread(mAwContents));
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView"})
    public void testPictureListener() throws Throwable {
        ThreadUtils.runOnUiThreadBlocking(() -> mAwContents.enableOnNewPicture(true, true));

        int pictureCount = mContentsClient.getPictureListenerHelper().getCallCount();
        mActivityTestRule.loadUrlSync(
                mAwContents,
                mContentsClient.getOnPageFinishedHelper(),
                ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL);
        mContentsClient.getPictureListenerHelper().waitForCallback(pictureCount, 1);
        // Invalidation only, so picture should be null.
        Assert.assertNull(mContentsClient.getPictureListenerHelper().getPicture());
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView"})
    public void testForceDrawWhenInvisible() throws Throwable {
        final String html =
                "<html><head><style>body {background-color:#227788}</style></head>"
                        + "<body>Hello world!</body></html>";
        // Loading the html via a data URI requires us to encode '#' symbols as '%23'.
        mActivityTestRule.loadUrlSync(
                mAwContents,
                mContentsClient.getOnPageFinishedHelper(),
                "data:text/html," + html.replace("#", "%23"));

        Bitmap visibleBitmap = null;
        Bitmap invisibleBitmap = null;
        final CountDownLatch latch = new CountDownLatch(1);
        InstrumentationRegistry.getInstrumentation()
                .runOnMainSync(
                        () -> {
                            final long requestId1 = 1;
                            mAwContents.insertVisualStateCallback(
                                    requestId1,
                                    new VisualStateCallback() {
                                        @Override
                                        public void onComplete(long id) {
                                            Assert.assertEquals(requestId1, id);
                                            latch.countDown();
                                        }
                                    });
                        });
        Assert.assertTrue(
                latch.await(AwActivityTestRule.SCALED_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));

        final int width = ThreadUtils.runOnUiThreadBlocking(() -> mContainerView.getWidth());
        final int height = ThreadUtils.runOnUiThreadBlocking(() -> mContainerView.getHeight());
        visibleBitmap = GraphicsTestUtils.drawAwContentsOnUiThread(mAwContents, width, height);

        // Things that affect DOM page visibility:
        // 1. isPaused
        // 2. window's visibility, if the webview is attached to a window.
        // Note android.view.View's visibility does not affect DOM page visibility.
        InstrumentationRegistry.getInstrumentation()
                .runOnMainSync(
                        () -> {
                            mContainerView.setVisibility(View.INVISIBLE);
                            Assert.assertTrue(mAwContents.isPageVisible());

                            mAwContents.onPause();
                            Assert.assertFalse(mAwContents.isPageVisible());

                            mAwContents.onResume();
                            Assert.assertTrue(mAwContents.isPageVisible());

                            // Simulate a window visibility change. WebView test app can't
                            // manipulate the window visibility directly.
                            mAwContents.onWindowVisibilityChanged(View.INVISIBLE);
                            Assert.assertFalse(mAwContents.isPageVisible());
                        });

        // VisualStateCallback#onComplete won't be called when WebView is
        // invisible. So there is no reliable way to tell if View#setVisibility
        // has taken effect. Just sleep the test thread for 500ms.
        Thread.sleep(500);
        invisibleBitmap = GraphicsTestUtils.drawAwContentsOnUiThread(mAwContents, width, height);
        Assert.assertNotNull(invisibleBitmap);
        Assert.assertTrue(invisibleBitmap.sameAs(visibleBitmap));
    }

    @Test
    @SmallTest
    @Feature({"AndroidWebView"})
    public void testSoftwareCanvas() throws Throwable {
        mAwContents.getSettings().setAllowFileAccess(true);
        ThreadUtils.runOnUiThreadBlocking(
                () -> mAwContents.setLayerType(View.LAYER_TYPE_SOFTWARE, null));

        String testFile = "android_webview/test/data/green_canvas.html";
        String url = UrlUtils.getIsolatedTestFileUrl(testFile);
        AwActivityTestRule.enableJavaScriptOnUiThread(mAwContents);
        mActivityTestRule.loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), url);
        mActivityTestRule.waitForVisualStateCallback(mAwContents);

        CriteriaHelper.pollInstrumentationThread(
                () -> {
                    Bitmap bitmap =
                            GraphicsTestUtils.drawAwContentsOnUiThread(mAwContents, 500, 500);
                    return Color.GREEN == bitmap.getPixel(250, 250);
                });
    }
}