Commit c4bd5cac authored by Scott Violet's avatar Scott Violet Committed by Commit Bot

weblayer: update top-control scrolling

Top-controls scrolling was missing a couple of key things:
. when the top-controls are completely visible (and no gesture/scroll is
  underway), the height of the Webcontents should shrink.
. DoBrowserControlsShrinkRendererSize() should not change value while
  a gesture/scroll is underway (otherwise the scrollbar bounces around).

The logic added should match that of Chrome.

BUG=1017204
TEST=none

Change-Id: I2185409244e278dfd230499b65ed2d953eda6d70
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1880589
Commit-Queue: Scott Violet <sky@chromium.org>
Reviewed-by: default avatarBo <boliu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#709559}
parent 802ccbc0
......@@ -58,6 +58,15 @@ void HandleJavaScriptResult(
} // namespace
#if defined(OS_ANDROID)
BrowserControllerImpl::BrowserControllerImpl(
ProfileImpl* profile,
const base::android::JavaParamRef<jobject>& java_impl)
: BrowserControllerImpl(profile) {
java_impl_ = java_impl;
}
#endif
BrowserControllerImpl::BrowserControllerImpl(ProfileImpl* profile)
: profile_(profile) {
#if defined(OS_ANDROID)
......@@ -142,10 +151,12 @@ void BrowserControllerImpl::AttachToView(views::WebView* web_view) {
#endif
#if defined(OS_ANDROID)
static jlong JNI_BrowserControllerImpl_CreateBrowserController(JNIEnv* env,
jlong profile) {
return reinterpret_cast<intptr_t>(
new BrowserControllerImpl(reinterpret_cast<ProfileImpl*>(profile)));
static jlong JNI_BrowserControllerImpl_CreateBrowserController(
JNIEnv* env,
jlong profile,
const base::android::JavaParamRef<jobject>& java_impl) {
return reinterpret_cast<intptr_t>(new BrowserControllerImpl(
reinterpret_cast<ProfileImpl*>(profile), java_impl));
}
static void JNI_BrowserControllerImpl_DeleteBrowserController(
......@@ -216,7 +227,12 @@ int BrowserControllerImpl::GetTopControlsHeight() {
bool BrowserControllerImpl::DoBrowserControlsShrinkRendererSize(
const content::WebContents* web_contents) {
return true;
#if defined(OS_ANDROID)
return Java_BrowserControllerImpl_doBrowserControlsShrinkRendererSize(
base::android::AttachCurrentThread(), java_impl_);
#else
return false;
#endif
}
bool BrowserControllerImpl::EmbedsFullscreenWidget() {
......
......@@ -36,6 +36,11 @@ class BrowserControllerImpl : public BrowserController,
public content::WebContentsDelegate,
public content::WebContentsObserver {
public:
// TODO(sky): investigate a better way to not have so many ifdefs.
#if defined(OS_ANDROID)
BrowserControllerImpl(ProfileImpl* profile,
const base::android::JavaParamRef<jobject>& java_impl);
#endif
explicit BrowserControllerImpl(ProfileImpl* profile);
~BrowserControllerImpl() override;
......@@ -115,6 +120,7 @@ class BrowserControllerImpl : public BrowserController,
base::ObserverList<BrowserObserver>::Unchecked observers_;
#if defined(OS_ANDROID)
TopControlsContainerView* top_controls_container_view_ = nullptr;
base::android::ScopedJavaGlobalRef<jobject> java_impl_;
#endif
bool is_fullscreen_ = false;
......
......@@ -21,6 +21,7 @@ android_library("java") {
"org/chromium/weblayer_private/BrowserFragmentControllerImpl.java",
"org/chromium/weblayer_private/BrowserFragmentImpl.java",
"org/chromium/weblayer_private/BrowserObserverProxy.java",
"org/chromium/weblayer_private/ChildProcessServiceImpl.java",
"org/chromium/weblayer_private/ContentView.java",
"org/chromium/weblayer_private/ContentViewRenderView.java",
"org/chromium/weblayer_private/DownloadDelegateProxy.java",
......@@ -30,10 +31,10 @@ android_library("java") {
"org/chromium/weblayer_private/ProfileImpl.java",
"org/chromium/weblayer_private/FragmentWindowAndroid.java",
"org/chromium/weblayer_private/ProfileManager.java",
"org/chromium/weblayer_private/RemoteFragmentImpl.java",
"org/chromium/weblayer_private/WebContentsGestureStateTracker.java",
"org/chromium/weblayer_private/TopControlsContainerView.java",
"org/chromium/weblayer_private/WebLayerImpl.java",
"org/chromium/weblayer_private/ChildProcessServiceImpl.java",
"org/chromium/weblayer_private/RemoteFragmentImpl.java",
]
deps = [
......
......@@ -14,6 +14,7 @@ import android.webkit.ValueCallback;
import android.widget.FrameLayout;
import org.chromium.base.Callback;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.content_public.browser.ViewEventSink;
......@@ -28,8 +29,13 @@ import org.chromium.weblayer_private.aidl.INavigationControllerClient;
import org.chromium.weblayer_private.aidl.IObjectWrapper;
import org.chromium.weblayer_private.aidl.ObjectWrapper;
/**
* Implementation of IBrowserController.
*/
@JNINamespace("weblayer")
public final class BrowserControllerImpl extends IBrowserController.Stub {
public final class BrowserControllerImpl extends IBrowserController.Stub
implements TopControlsContainerView.Listener,
WebContentsGestureStateTracker.OnGestureStateChangedListener {
private long mNativeBrowserController;
// TODO: move mContentViewRenderView, mContentView, mTopControlsContainerView to
......@@ -46,6 +52,13 @@ public final class BrowserControllerImpl extends IBrowserController.Stub {
private NavigationControllerImpl mNavigationController;
private DownloadDelegateProxy mDownloadDelegateProxy;
private FullscreenDelegateProxy mFullscreenDelegateProxy;
private WebContentsGestureStateTracker mGestureStateTracker;
/**
* The value of mCachedDoBrowserControlsShrinkRendererSize is set when
* WebContentsGestureStateTracker begins a gesture. This is necessary as the values should only
* change once a gesture is no longer under way.
*/
private boolean mCachedDoBrowserControlsShrinkRendererSize;
private static class InternalAccessDelegateImpl
implements ViewEventSink.InternalAccessDelegate {
......@@ -77,12 +90,12 @@ public final class BrowserControllerImpl extends IBrowserController.Stub {
mContentViewRenderView.onNativeLibraryLoaded(
windowAndroid, ContentViewRenderView.MODE_SURFACE_VIEW);
mNativeBrowserController =
BrowserControllerImplJni.get().createBrowserController(profile.getNativeProfile());
mNativeBrowserController = BrowserControllerImplJni.get().createBrowserController(
profile.getNativeProfile(), this);
mWebContents = BrowserControllerImplJni.get().getWebContents(
mNativeBrowserController, BrowserControllerImpl.this);
mTopControlsContainerView =
new TopControlsContainerView(context, mWebContents, mContentViewRenderView);
new TopControlsContainerView(context, mWebContents, mContentViewRenderView, this);
mContentView = ContentView.createContentView(
context, mWebContents, mTopControlsContainerView.getEventOffsetHandler());
ViewAndroidDelegate viewAndroidDelegate = new ViewAndroidDelegate(mContentView) {
......@@ -109,6 +122,8 @@ public final class BrowserControllerImpl extends IBrowserController.Stub {
mWebContents.onShow();
mContentView.requestFocus();
mGestureStateTracker = new WebContentsGestureStateTracker(mContentView, mWebContents, this);
}
long getNativeBrowserController() {
......@@ -123,6 +138,29 @@ public final class BrowserControllerImpl extends IBrowserController.Stub {
return mNavigationController;
}
@Override
public void onTopControlsCompletelyShownOrHidden() {
adjustWebContentsHeightIfNecessary();
}
@Override
public void onGestureStateChanged() {
if (mGestureStateTracker.isInGestureOrScroll()) {
mCachedDoBrowserControlsShrinkRendererSize =
mTopControlsContainerView.isTopControlVisible();
}
adjustWebContentsHeightIfNecessary();
}
private void adjustWebContentsHeightIfNecessary() {
if (mGestureStateTracker.isInGestureOrScroll()
|| !mTopControlsContainerView.isTopControlsCompletelyShownOrHidden()) {
return;
}
mContentViewRenderView.setWebContentsHeightDelta(
mTopControlsContainerView.getTopContentOffset());
}
@Override
public void setClient(IBrowserControllerClient client) {
mBrowserObserverProxy = new BrowserObserverProxy(mNativeBrowserController, client);
......@@ -191,6 +229,7 @@ public final class BrowserControllerImpl extends IBrowserController.Stub {
mFullscreenDelegateProxy.destroy();
mFullscreenDelegateProxy = null;
}
mGestureStateTracker.destroy();
mNavigationController = null;
BrowserControllerImplJni.get().deleteBrowserController(mNativeBrowserController);
mNativeBrowserController = 0;
......@@ -212,9 +251,16 @@ public final class BrowserControllerImpl extends IBrowserController.Stub {
(ValueCallback<Boolean>) ObjectWrapper.unwrap(callback, ValueCallback.class));
}
@CalledByNative
private boolean doBrowserControlsShrinkRendererSize() {
return (mGestureStateTracker.isInGestureOrScroll())
? mCachedDoBrowserControlsShrinkRendererSize
: mTopControlsContainerView.isTopControlVisible();
}
@NativeMethods
interface Natives {
long createBrowserController(long profile);
long createBrowserController(long profile, BrowserControllerImpl caller);
void setTopControlsContainerView(long nativeBrowserControllerImpl,
BrowserControllerImpl caller, long nativeTopControlsContainerView);
void deleteBrowserController(long browserController);
......
......@@ -62,6 +62,8 @@ public class ContentViewRenderView extends FrameLayout {
private int mWidth;
private int mHeight;
private int mWebContentsHeightDelta;
// Common interface to listen to surface related events.
private interface SurfaceEventListener {
void surfaceCreated();
......@@ -514,11 +516,25 @@ public class ContentViewRenderView extends FrameLayout {
mRequested.addCallback(callback);
}
/**
* Sets how much to decrease the height of the WebContents by.
*/
public void setWebContentsHeightDelta(int delta) {
if (delta == mWebContentsHeightDelta) return;
mWebContentsHeightDelta = delta;
updateWebContentsSize();
}
private void updateWebContentsSize() {
if (mWebContents == null) return;
mWebContents.setSize(mWidth, mHeight - mWebContentsHeightDelta);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mWidth = w;
mHeight = h;
if (mWebContents != null) mWebContents.setSize(w, h);
updateWebContentsSize();
}
/**
......@@ -581,7 +597,7 @@ public class ContentViewRenderView extends FrameLayout {
mWebContents = webContents;
if (webContents != null) {
webContents.setSize(mWidth, mHeight);
updateWebContentsSize();
ContentViewRenderViewJni.get().onPhysicalBackingSizeChanged(
mNativeContentViewRenderView, webContents, mWidth, mHeight);
}
......
......@@ -26,6 +26,30 @@ import org.chromium.ui.resources.dynamics.ViewResourceAdapter;
* bitmap is placed in a cc::Layer and the layer is shown while scrolling the top-view.
* ViewResourceAdapter is always kept in sync, as to do otherwise results in a noticeable delay
* between when the scroll starts the content is available.
*
* There are many parts involved in orchestrating top-controls scrolling. The key things to know
* are:
* . TopControlsContainerView (in native code) keeps a cc::Layer that shows a bitmap rendered by
* the top-view. The bitmap is updated anytime the top-view changes. This is done as otherwise
* there is a noticable delay between when the scroll starts and the bitmap is available.
* . When scrolling, the cc::Layer for the WebContents and TopControlsContainerView is moved.
* . The size of the WebContents is only changed after the user releases a touch point. Otherwise
* the scrollbar bounces around.
* . WebContentsDelegate::DoBrowserControlsShrinkRendererSize() only changes when the WebContents
* size change.
* . WebContentsGestureStateTracker is responsible for determining when a scroll/touch is underway.
* . ContentViewRenderView.Delegate is used to adjust the size of the webcontents when the
* top-controls are fully visible (and a scroll is not underway).
*
* The flow of this code is roughly:
* . WebContentsGestureStateTracker generally detects a touch first
* . BrowserControllerImpl is notified and caches state.
* . onTopControlsChanged() is called. This triggers hiding the real view and calling to native code
* to move the cc::Layers.
* . the move continues.
* . when the move completes and both WebContentsGestureStateTracker and TopControlsContainerView
* no longer believe a move/gesture/scroll is underway the size of the WebContents is adjusted
* (if necessary).
*/
@JNINamespace("weblayer")
class TopControlsContainerView extends FrameLayout {
......@@ -50,7 +74,9 @@ class TopControlsContainerView extends FrameLayout {
private EventOffsetHandler mEventOffsetHandler;
private int mTopContentOffset;
// True if scrolling.
// Set to true if |mView| is hidden because the user has scrolled or triggered some action such
// that mView is not visible. While |mView| is not visible if this is true, the bitmap from
// |mView| may be partially visible.
private boolean mInTopControlsScroll;
private boolean mIsFullscreen;
......@@ -58,6 +84,15 @@ class TopControlsContainerView extends FrameLayout {
// Used to delay processing fullscreen requests.
private Runnable mSystemUiFullscreenResizeRunnable;
private final Listener mListener;
public interface Listener {
/**
* Called when the top-controls are either completely showing, or completely hiding.
*/
public void onTopControlsCompletelyShownOrHidden();
}
// Used to delay updating the image for the layer.
private final Runnable mRefreshResourceIdRunnable = () -> {
if (mView == null) return;
......@@ -65,8 +100,8 @@ class TopControlsContainerView extends FrameLayout {
mNativeTopControlsContainerView, TopControlsContainerView.this);
};
TopControlsContainerView(
Context context, WebContents webContents, ContentViewRenderView contentViewRenderView) {
TopControlsContainerView(Context context, WebContents webContents,
ContentViewRenderView contentViewRenderView, Listener listener) {
super(context);
mContentViewRenderView = contentViewRenderView;
mWebContents = webContents;
......@@ -85,6 +120,7 @@ class TopControlsContainerView extends FrameLayout {
mNativeTopControlsContainerView =
TopControlsContainerViewJni.get().createTopControlsContainerView(
this, webContents, contentViewRenderView.getNativeHandle());
mListener = listener;
}
public void destroy() {
......@@ -101,6 +137,21 @@ class TopControlsContainerView extends FrameLayout {
return mEventOffsetHandler;
}
/**
* Returns the vertical offset for the WebContents.
*/
public int getTopContentOffset() {
return mView == null ? 0 : mTopContentOffset;
}
/**
* Returns true if the top control is visible to the user.
*/
public boolean isTopControlVisible() {
// Don't check the visibility of the View itself as it's hidden while scrolling.
return mView != null && mTopContentOffset != 0;
}
/**
* Sets the view from the client.
*/
......@@ -216,8 +267,19 @@ class TopControlsContainerView extends FrameLayout {
mContentViewRenderView.postOnAnimation(() -> showTopControls());
}
/**
* Returns true if the top-controls are completely shown or completely hidden. A return value
* of false indicates the top-controls are being moved.
*/
public boolean isTopControlsCompletelyShownOrHidden() {
return mTopContentOffset == 0 || mTopContentOffset == getHeight();
}
private void setTopControlsOffset(int topControlsOffsetY, int topContentOffsetY) {
mTopContentOffset = topContentOffsetY;
if (isTopControlsCompletelyShownOrHidden()) {
mListener.onTopControlsCompletelyShownOrHidden();
}
TopControlsContainerViewJni.get().setTopControlsOffset(mNativeTopControlsContainerView,
TopControlsContainerView.this, topControlsOffsetY, topContentOffsetY);
}
......
// Copyright 2019 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_private;
import android.view.MotionEvent;
import android.view.View;
import org.chromium.content_public.browser.GestureListenerManager;
import org.chromium.content_public.browser.GestureStateListener;
import org.chromium.content_public.browser.WebContents;
/**
* WebContentsGestureStateTracker is responsible for tracking when a scroll/gesture is in progress
* and notifying when the state changes.
*/
// TODO(sky): refactor TabGestureStateListener and this to a common place.
public final class WebContentsGestureStateTracker {
private GestureListenerManager mGestureListenerManager;
private GestureStateListener mGestureListener;
private final OnGestureStateChangedListener mListener;
private boolean mScrolling;
private boolean mIsInGesture;
/**
* The View events are tracked on.
*/
private View mContentView;
/**
* Notified when the gesture state changes.
*/
public interface OnGestureStateChangedListener {
/**
* Called when the value of isInGestureOrScroll() changes.
*/
public void onGestureStateChanged();
}
public WebContentsGestureStateTracker(
View contentView, WebContents webContents, OnGestureStateChangedListener listener) {
mListener = listener;
mGestureListenerManager = GestureListenerManager.fromWebContents(webContents);
mContentView = contentView;
mContentView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent event) {
final int eventAction = event.getActionMasked();
final boolean oldState = isInGestureOrScroll();
if (eventAction == MotionEvent.ACTION_DOWN
|| eventAction == MotionEvent.ACTION_POINTER_DOWN) {
mIsInGesture = true;
} else if (eventAction == MotionEvent.ACTION_CANCEL
|| eventAction == MotionEvent.ACTION_UP) {
mIsInGesture = false;
}
if (isInGestureOrScroll() != oldState) {
mListener.onGestureStateChanged();
}
return false;
}
});
mGestureListener = new GestureStateListener() {
@Override
public void onFlingStartGesture(int scrollOffsetY, int scrollExtentY) {
onScrollingStateChanged();
}
@Override
public void onFlingEndGesture(int scrollOffsetY, int scrollExtentY) {
onScrollingStateChanged();
}
@Override
public void onScrollStarted(int scrollOffsetY, int scrollExtentY) {
onScrollingStateChanged();
}
@Override
public void onScrollEnded(int scrollOffsetY, int scrollExtentY) {
onScrollingStateChanged();
}
private void onScrollingStateChanged() {
final boolean oldState = isInGestureOrScroll();
mScrolling = mGestureListenerManager.isScrollInProgress();
if (oldState != isInGestureOrScroll()) {
mListener.onGestureStateChanged();
}
}
};
mGestureListenerManager.addListener(mGestureListener);
}
public void destroy() {
mGestureListenerManager.removeListener(mGestureListener);
mGestureListener = null;
mGestureListenerManager = null;
mContentView.setOnTouchListener(null);
}
/**
* Returns true if the user has touched the target view, or is scrolling.
*/
public boolean isInGestureOrScroll() {
return mIsInGesture || mScrolling;
}
}
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