chromium/content/public/android/javatests/src/org/chromium/content/browser/GestureListenerManagerTest.java

// Copyright 2020 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.content.browser;

import static org.chromium.cc.mojom.RootScrollOffsetUpdateFrequency.ALL_UPDATES;
import static org.chromium.cc.mojom.RootScrollOffsetUpdateFrequency.NONE;

import android.view.View;

import androidx.test.filters.LargeTest;

import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.chromium.base.ThreadUtils;
import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.Criteria;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.base.test.util.DisabledTest;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.UrlUtils;
import org.chromium.content_public.browser.GestureListenerManager;
import org.chromium.content_public.browser.GestureStateListener;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.browser.test.RenderFrameHostTestExt;
import org.chromium.content_public.browser.test.util.TestCallbackHelperContainer;
import org.chromium.content_public.browser.test.util.TouchCommon;
import org.chromium.content_shell_apk.ContentShellActivityTestRule;

/** Assertions for GestureListenerManager. */
@RunWith(BaseJUnit4ClassRunner.class)
public class GestureListenerManagerTest {
    @Rule
    public ContentShellActivityTestRule mActivityTestRule = new ContentShellActivityTestRule();

    // The page should be large enough so that scrolling occurs.
    private static final String TEST_URL =
            UrlUtils.encodeHtmlDataUri(
                    "<html><body style='height: 10000px'><script>window.addEventListener('load', ()"
                            + " => { document.title = 'loaded'; });</script>");

    private static final class GestureStateListenerImpl extends GestureStateListener {
        private int mNumOnScrollOffsetOrExtentChangedCalls;
        public CallbackHelper mCallbackHelper = new CallbackHelper();
        private boolean mGotStarted;
        private boolean mDidScrollOffsetChangeWhileScrolling;
        private Integer mLastScrollOffsetY;

        @Override
        public void onScrollStarted(int scrollOffsetY, int scrollExtentY, boolean isDirectionUp) {
            org.chromium.base.Log.e("chrome", "!!!onScrollStarted " + scrollOffsetY);
            mGotStarted = true;
            mLastScrollOffsetY = null;
        }

        @Override
        public void onScrollOffsetOrExtentChanged(int scrollOffsetY, int scrollExtentY) {
            org.chromium.base.Log.e(
                    "chrome",
                    "!!!onScrollOffsetOrExtentChanged started="
                            + mGotStarted
                            + " scroll="
                            + scrollOffsetY
                            + " last="
                            + mLastScrollOffsetY);
            if (mGotStarted
                    && (mLastScrollOffsetY == null || !mLastScrollOffsetY.equals(scrollOffsetY))) {
                if (mLastScrollOffsetY != null) mDidScrollOffsetChangeWhileScrolling = true;
                mLastScrollOffsetY = scrollOffsetY;
            }
        }

        @Override
        public void onScrollEnded(int scrollOffsetY, int scrollExtentY) {
            org.chromium.base.Log.e("chrome", "!!!onScrollEnded, offset=" + scrollOffsetY);
            // onScrollEnded() should be preceded by onScrollStarted().
            Assert.assertTrue(mGotStarted);
            // onScrollOffsetOrExtentChanged() should be called at least twice. Once with an initial
            // value, and then with a different value.
            Assert.assertTrue(mDidScrollOffsetChangeWhileScrolling);
            mCallbackHelper.notifyCalled();
            mGotStarted = false;
        }
    }

    private float mCurrentX;
    private float mCurrentY;

    /** Assertions for GestureStateListener.onScrollOffsetOrExtentChanged. */
    @Test
    @LargeTest
    @Feature({"Browser"})
    @DisabledTest(message = "https://crbug.com/1324302")
    public void testOnScrollOffsetOrExtentChanged() throws Throwable {
        mActivityTestRule.launchContentShellWithUrl("about:blank");
        WebContents webContents = mActivityTestRule.getWebContents();
        // This needs to wait for first-paint, otherwise scrolling doesn't happen.
        TestCallbackHelperContainer callbackHelperContainer =
                new TestCallbackHelperContainer(webContents);
        mActivityTestRule.loadUrl(
                webContents.getNavigationController(),
                callbackHelperContainer,
                new LoadUrlParams(TEST_URL));
        // Wait for the first non-empty visual paint and the title to change. The title changes when
        // the doc has finished loading, which is a good signal events can be processed.
        callbackHelperContainer.getOnFirstVisuallyNonEmptyPaintHelper().waitForCallback(0);
        CriteriaHelper.pollUiThread(
                () -> Criteria.checkThat(webContents.getTitle(), Matchers.is("loaded")));

        // At this point the page has finished loading and a non-empty paint occurred. This does not
        // mean the renderer is fully ready to process events (processing events requires layers,
        // which may not have been created yet). Wait for a visual update, which should ensure the
        // renderer is ready.
        CallbackHelper callbackHelper = new CallbackHelper();
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    new RenderFrameHostTestExt(webContents.getMainFrame())
                            .updateVisualState(
                                    (Boolean result) -> {
                                        Assert.assertTrue(result);
                                        callbackHelper.notifyCalled();
                                    });
                });
        callbackHelper.waitForOnly();

        final GestureStateListenerImpl listener = new GestureStateListenerImpl();
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    GestureListenerManagerImpl manager =
                            (GestureListenerManagerImpl)
                                    GestureListenerManager.fromWebContents(webContents);
                    // getRootScrollOffsetUpdateFrequency() should initially return NONE (as there
                    // are no listeners).
                    Assert.assertEquals(
                            NONE, manager.getRootScrollOffsetUpdateFrequencyForTesting());
                    manager.addListener(listener, ALL_UPDATES);
                    // Adding a listener changes this to ALL_UPDATES.
                    Assert.assertEquals(
                            ALL_UPDATES, manager.getRootScrollOffsetUpdateFrequencyForTesting());
                    View webContentsView = webContents.getViewAndroidDelegate().getContainerView();
                    mCurrentX = webContentsView.getWidth() / 2;
                    mCurrentY = webContentsView.getHeight() / 2;
                    Assert.assertTrue(mCurrentY > 0);
                });

        // Perform a scroll.
        TouchCommon.performDrag(
                mActivityTestRule.getActivity(),
                mCurrentX,
                mCurrentX,
                mCurrentY,
                mCurrentY - 50,
                /* stepCount= */ 3, /* duration in ms */
                250);
        listener.mCallbackHelper.waitForCallback(0);

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    GestureListenerManagerImpl manager =
                            (GestureListenerManagerImpl)
                                    GestureListenerManager.fromWebContents(webContents);
                    manager.removeListener(listener);
                    // Should go back to NONE after removing the only listener.
                    Assert.assertEquals(
                            NONE, manager.getRootScrollOffsetUpdateFrequencyForTesting());
                });
    }
}