Commit 47d6b448 authored by Scott Violet's avatar Scott Violet Committed by Commit Bot

weblayer: adds ScrollOffsetCallback

This allows tracking the scrollTop of the page.

BUG=1109035
TEST=covered by tests

Change-Id: I34dd29cacb2569704176d8d3027347cdbd9be734
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2422650
Commit-Queue: Scott Violet <sky@chromium.org>
Reviewed-by: default avatarBo <boliu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#809596}
parent ddc23b76
...@@ -30,6 +30,7 @@ android_library("weblayer_java_tests") { ...@@ -30,6 +30,7 @@ android_library("weblayer_java_tests") {
"src/org/chromium/weblayer/test/OnTabRemovedTabListCallbackImpl.java", "src/org/chromium/weblayer/test/OnTabRemovedTabListCallbackImpl.java",
"src/org/chromium/weblayer/test/ProfileTest.java", "src/org/chromium/weblayer/test/ProfileTest.java",
"src/org/chromium/weblayer/test/RenderingTest.java", "src/org/chromium/weblayer/test/RenderingTest.java",
"src/org/chromium/weblayer/test/ScrollOffsetCallbackTest.java",
"src/org/chromium/weblayer/test/SmokeTest.java", "src/org/chromium/weblayer/test/SmokeTest.java",
"src/org/chromium/weblayer/test/TabCallbackTest.java", "src/org/chromium/weblayer/test/TabCallbackTest.java",
"src/org/chromium/weblayer/test/TabListCallbackTest.java", "src/org/chromium/weblayer/test/TabListCallbackTest.java",
......
// Copyright 2020 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.weblayer.test;
import androidx.test.filters.SmallTest;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.weblayer.ScrollOffsetCallback;
import org.chromium.weblayer.shell.InstrumentationActivity;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* Test for ScrollOffsetCallback.
*/
@RunWith(WebLayerJUnit4ClassRunner.class)
public class ScrollOffsetCallbackTest {
@Rule
public InstrumentationActivityTestRule mActivityTestRule =
new InstrumentationActivityTestRule();
@Test
@SmallTest
@MinWebLayerVersion(87)
public void testBasic() throws Throwable {
final String url = mActivityTestRule.getTestDataURL("tall_page.html");
InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(url);
// As registering for scroll offsets requires sending a message to the renderer, and
// requires viz to be in a particular state, ensuring scrolls will generate a message is a
// bit finicky. This tries to adjust the top-offset 10 times before giving up.
CallbackHelper callbackHelper = new CallbackHelper();
ScrollOffsetCallback scrollOffsetCallback = new ScrollOffsetCallback() {
@Override
public void onVerticalScrollOffsetChanged(int scrollOffset) {
callbackHelper.notifyCalled();
}
};
TestThreadUtils.runOnUiThreadBlocking(() -> {
activity.getBrowser().getActiveTab().registerScrollOffsetCallback(scrollOffsetCallback);
});
boolean gotInitialScroll = false;
for (int i = 1; i < 10 && !gotInitialScroll; ++i) {
mActivityTestRule.executeScriptSync("window.scroll(0, " + (i * 100) + ");", false);
try {
callbackHelper.waitForCallback(
"Waiting for initial scroll", 0, 1, 100, TimeUnit.MILLISECONDS);
gotInitialScroll = true;
} catch (TimeoutException e) {
}
}
if (!gotInitialScroll) {
Assert.fail("Was unable to get initial scroll, failing");
}
// Scroll back to the origin.
CallbackHelper scrollBackCallbackHelper = new CallbackHelper();
ScrollOffsetCallback scrollBackOffsetCallback = new ScrollOffsetCallback() {
@Override
public void onVerticalScrollOffsetChanged(int scrollOffset) {
if (scrollOffset == 0) {
scrollBackCallbackHelper.notifyCalled();
}
}
};
TestThreadUtils.runOnUiThreadBlocking(() -> {
activity.getBrowser().getActiveTab().registerScrollOffsetCallback(
scrollBackOffsetCallback);
});
mActivityTestRule.executeScriptSync("window.scroll(0, 0);", false);
scrollBackCallbackHelper.waitForFirst();
TestThreadUtils.runOnUiThreadBlocking(() -> {
activity.getBrowser().getActiveTab().unregisterScrollOffsetCallback(
scrollOffsetCallback);
});
// At this point scrollOffsetCallback is still registered. If this code were to remove the
// callback the renderer might temporarily disable scroll-offsets notification, which
// could potentially lead to raciness. In other words, best to leave it for future
// assertions.
}
// NOTE: if adding another test, you'll likely want to make testBasic() a setUp type function
// that is shared.
}
...@@ -41,6 +41,8 @@ import org.chromium.components.find_in_page.FindMatchRectsDetails; ...@@ -41,6 +41,8 @@ import org.chromium.components.find_in_page.FindMatchRectsDetails;
import org.chromium.components.find_in_page.FindResultBar; import org.chromium.components.find_in_page.FindResultBar;
import org.chromium.components.infobars.InfoBar; import org.chromium.components.infobars.InfoBar;
import org.chromium.components.url_formatter.UrlFormatter; import org.chromium.components.url_formatter.UrlFormatter;
import org.chromium.content_public.browser.GestureListenerManager;
import org.chromium.content_public.browser.GestureStateListenerWithScroll;
import org.chromium.content_public.browser.LoadUrlParams; import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.NavigationHandle; import org.chromium.content_public.browser.NavigationHandle;
import org.chromium.content_public.browser.SelectionClient; import org.chromium.content_public.browser.SelectionClient;
...@@ -134,6 +136,9 @@ public final class TabImpl extends ITab.Stub implements LoginPrompt.Observer { ...@@ -134,6 +136,9 @@ public final class TabImpl extends ITab.Stub implements LoginPrompt.Observer {
private Set<FaviconCallbackProxy> mFaviconCallbackProxies = new HashSet<>(); private Set<FaviconCallbackProxy> mFaviconCallbackProxies = new HashSet<>();
// Only non-null if scroll offsets have been requested.
private @Nullable GestureStateListenerWithScroll mGestureStateListenerWithScroll;
private static class InternalAccessDelegateImpl private static class InternalAccessDelegateImpl
implements ViewEventSink.InternalAccessDelegate { implements ViewEventSink.InternalAccessDelegate {
@Override @Override
...@@ -570,6 +575,31 @@ public final class TabImpl extends ITab.Stub implements LoginPrompt.Observer { ...@@ -570,6 +575,31 @@ public final class TabImpl extends ITab.Stub implements LoginPrompt.Observer {
TabImplJni.get().setTranslateTargetLanguage(mNativeTab, targetLanguage); TabImplJni.get().setTranslateTargetLanguage(mNativeTab, targetLanguage);
} }
@Override
public void setScrollOffsetsEnabled(boolean enabled) {
if (enabled) {
if (mGestureStateListenerWithScroll == null) {
mGestureStateListenerWithScroll = new GestureStateListenerWithScroll() {
@Override
public void onScrollOffsetOrExtentChanged(
int scrollOffsetY, int scrollExtentY) {
try {
mClient.onVerticalScrollOffsetChanged(scrollOffsetY);
} catch (RemoteException e) {
throw new APICallException(e);
}
}
};
GestureListenerManager.fromWebContents(mWebContents)
.addListener(mGestureStateListenerWithScroll);
}
} else if (mGestureStateListenerWithScroll != null) {
GestureListenerManager.fromWebContents(mWebContents)
.removeListener(mGestureStateListenerWithScroll);
mGestureStateListenerWithScroll = null;
}
}
public void removeFaviconCallbackProxy(FaviconCallbackProxy proxy) { public void removeFaviconCallbackProxy(FaviconCallbackProxy proxy) {
mFaviconCallbackProxies.remove(proxy); mFaviconCallbackProxies.remove(proxy);
} }
...@@ -873,6 +903,9 @@ public final class TabImpl extends ITab.Stub implements LoginPrompt.Observer { ...@@ -873,6 +903,9 @@ public final class TabImpl extends ITab.Stub implements LoginPrompt.Observer {
} }
} }
// This is called to ensure a listener is removed from the WebContents.
setScrollOffsetsEnabled(false);
if (mTabCallbackProxy != null) { if (mTabCallbackProxy != null) {
mTabCallbackProxy.destroy(); mTabCallbackProxy.destroy();
mTabCallbackProxy = null; mTabCallbackProxy = null;
......
...@@ -72,4 +72,7 @@ interface ITab { ...@@ -72,4 +72,7 @@ interface ITab {
void setGoogleAccountsCallbackClient(IGoogleAccountsCallbackClient client) = 23; void setGoogleAccountsCallbackClient(IGoogleAccountsCallbackClient client) = 23;
IFaviconFetcher createFaviconFetcher(IFaviconFetcherClient client) = 24; IFaviconFetcher createFaviconFetcher(IFaviconFetcherClient client) = 24;
void setTranslateTargetLanguage(in String targetLanguage) = 25; void setTranslateTargetLanguage(in String targetLanguage) = 25;
// Added in 87
void setScrollOffsetsEnabled(in boolean enabled) = 26;
} }
...@@ -43,4 +43,7 @@ interface ITabClient { ...@@ -43,4 +43,7 @@ interface ITabClient {
// Added in M85 // Added in M85
void onScrollNotification( void onScrollNotification(
in int notificationType, in float currentScrollRatio) = 10; in int notificationType, in float currentScrollRatio) = 10;
// Added in M87
void onVerticalScrollOffsetChanged(in int offset) = 11;
} }
...@@ -79,6 +79,7 @@ android_library("java") { ...@@ -79,6 +79,7 @@ android_library("java") {
"org/chromium/weblayer/Profile.java", "org/chromium/weblayer/Profile.java",
"org/chromium/weblayer/RemoteFragment.java", "org/chromium/weblayer/RemoteFragment.java",
"org/chromium/weblayer/ScrollNotificationType.java", "org/chromium/weblayer/ScrollNotificationType.java",
"org/chromium/weblayer/ScrollOffsetCallback.java",
"org/chromium/weblayer/SettingType.java", "org/chromium/weblayer/SettingType.java",
"org/chromium/weblayer/SiteSettingsActivity.java", "org/chromium/weblayer/SiteSettingsActivity.java",
"org/chromium/weblayer/SiteSettingsFragment.java", "org/chromium/weblayer/SiteSettingsFragment.java",
......
// Copyright 2020 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.weblayer;
/**
* Callback notified when the vertical location of the content of a Tab changes. The value reported
* by the callback corresponds to the 'scrollTop' html property.
*
* WARNING: use of this API necessitates additional cross process ipc that impacts overall
* performance. Only use when absolutely necessary.
*
* Because of WebLayer's multi-process architecture, this function can not be used to reliably
* synchronize the painting of other Views with WebLayer's Views. It's entirely possible one will
* render before or after the other.
*
* @since 87
*/
public abstract class ScrollOffsetCallback {
/**
* Called when the scroll offset of the content of a Tab changes.
*/
public abstract void onVerticalScrollOffsetChanged(int scrollOffset);
}
...@@ -55,6 +55,7 @@ public class Tab { ...@@ -55,6 +55,7 @@ public class Tab {
private Profile.DownloadCallbackClientImpl mDownloadCallbackClient; private Profile.DownloadCallbackClientImpl mDownloadCallbackClient;
private FullscreenCallbackClientImpl mFullscreenCallbackClient; private FullscreenCallbackClientImpl mFullscreenCallbackClient;
private NewTabCallback mNewTabCallback; private NewTabCallback mNewTabCallback;
private final ObserverList<ScrollOffsetCallback> mScrollOffsetCallbacks;
// Id from the remote side. // Id from the remote side.
private final int mId; private final int mId;
...@@ -65,6 +66,7 @@ public class Tab { ...@@ -65,6 +66,7 @@ public class Tab {
mFindInPageController = null; mFindInPageController = null;
mMediaCaptureController = null; mMediaCaptureController = null;
mCallbacks = null; mCallbacks = null;
mScrollOffsetCallbacks = null;
mId = 0; mId = 0;
} }
...@@ -79,6 +81,7 @@ public class Tab { ...@@ -79,6 +81,7 @@ public class Tab {
} }
mCallbacks = new ObserverList<TabCallback>(); mCallbacks = new ObserverList<TabCallback>();
mScrollOffsetCallbacks = new ObserverList<ScrollOffsetCallback>();
mNavigationController = NavigationController.create(mImpl); mNavigationController = NavigationController.create(mImpl);
mFindInPageController = new FindInPageController(mImpl); mFindInPageController = new FindInPageController(mImpl);
mMediaCaptureController = new MediaCaptureController(mImpl); mMediaCaptureController = new MediaCaptureController(mImpl);
...@@ -353,6 +356,48 @@ public class Tab { ...@@ -353,6 +356,48 @@ public class Tab {
mCallbacks.removeObserver(callback); mCallbacks.removeObserver(callback);
} }
/**
* Registers {@link callback} to be notified when the scroll offset changes. <b>WARNING:</b>
* adding a {@link ScrollOffsetCallback} impacts performance, ensure
* {@link ScrollOffsetCallback} are only installed when needed. See {@link ScrollOffsetCallback}
* for more details.
*
* @param callback The ScrollOffsetCallback to notify
*
* @since 87
*/
public void registerScrollOffsetCallback(@NonNull ScrollOffsetCallback callback) {
ThreadCheck.ensureOnUiThread();
throwIfDestroyed();
if (WebLayer.getSupportedMajorVersionInternal() < 87) {
throw new UnsupportedOperationException();
}
if (mScrollOffsetCallbacks.isEmpty()) {
try {
mImpl.setScrollOffsetsEnabled(true);
} catch (RemoteException e) {
throw new APICallException(e);
}
}
mScrollOffsetCallbacks.addObserver(callback);
}
public void unregisterScrollOffsetCallback(@NonNull ScrollOffsetCallback callback) {
ThreadCheck.ensureOnUiThread();
throwIfDestroyed();
if (WebLayer.getSupportedMajorVersionInternal() < 87) {
throw new UnsupportedOperationException();
}
mScrollOffsetCallbacks.removeObserver(callback);
if (mScrollOffsetCallbacks.isEmpty()) {
try {
mImpl.setScrollOffsetsEnabled(false);
} catch (RemoteException e) {
throw new APICallException(e);
}
}
}
/** /**
* Take a screenshot of this tab and return it as a Bitmap. * Take a screenshot of this tab and return it as a Bitmap.
* This API captures only the web content, not any Java Views, including the * This API captures only the web content, not any Java Views, including the
...@@ -821,6 +866,14 @@ public class Tab { ...@@ -821,6 +866,14 @@ public class Tab {
callback.onScrollNotification(notificationType, currentScrollRatio); callback.onScrollNotification(notificationType, currentScrollRatio);
} }
} }
@Override
public void onVerticalScrollOffsetChanged(int value) {
StrictModeWorkaround.apply();
for (ScrollOffsetCallback callback : mScrollOffsetCallbacks) {
callback.onVerticalScrollOffsetChanged(value);
}
}
} }
private static final class ErrorPageCallbackClientImpl extends IErrorPageCallbackClient.Stub { private static final class ErrorPageCallbackClientImpl extends IErrorPageCallbackClient.Stub {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment