diff options
Diffstat (limited to 'android_webview')
12 files changed, 657 insertions, 37 deletions
diff --git a/android_webview/browser/browser_view_renderer.h b/android_webview/browser/browser_view_renderer.h index ab0a59e..20f3782 100644 --- a/android_webview/browser/browser_view_renderer.h +++ b/android_webview/browser/browser_view_renderer.h @@ -43,6 +43,9 @@ class BrowserViewRenderer { // Try to set the view's scroll offset to |new_value|. virtual void ScrollContainerViewTo(gfx::Vector2d new_value) = 0; + // Handle overscroll. + virtual void DidOverscroll(gfx::Vector2d overscroll_delta) = 0; + protected: virtual ~Client() {} }; diff --git a/android_webview/browser/in_process_view_renderer.cc b/android_webview/browser/in_process_view_renderer.cc index 00e06ac..c737b65 100644 --- a/android_webview/browser/in_process_view_renderer.cc +++ b/android_webview/browser/in_process_view_renderer.cc @@ -673,6 +673,8 @@ void InProcessViewRenderer::ScrollTo(gfx::Vector2d new_value) { void InProcessViewRenderer::SetTotalRootLayerScrollOffset( gfx::Vector2dF new_value_css) { + previous_accumulated_overscroll_ = gfx::Vector2dF(); + // TOOD(mkosiba): Add a DCHECK to say that this does _not_ get called during // DrawGl when http://crbug.com/249972 is fixed. if (scroll_offset_css_ == new_value_css) @@ -692,6 +694,18 @@ gfx::Vector2dF InProcessViewRenderer::GetTotalRootLayerScrollOffset() { return scroll_offset_css_; } +void InProcessViewRenderer::DidOverscroll( + gfx::Vector2dF accumulated_overscroll, + gfx::Vector2dF current_fling_velocity) { + // TODO(mkosiba): Enable this once flinging is handled entirely Java-side. + // DCHECK(current_fling_velocity.IsZero()); + gfx::Vector2d overscroll_delta = gfx::ToRoundedVector2d(gfx::ScaleVector2d( + accumulated_overscroll - previous_accumulated_overscroll_, dip_scale_)); + previous_accumulated_overscroll_ += + gfx::ScaleVector2d(overscroll_delta, 1.0f / dip_scale_); + client_->DidOverscroll(overscroll_delta); +} + void InProcessViewRenderer::EnsureContinuousInvalidation( AwDrawGLInfo* draw_info) { if (continuous_invalidate_ && !block_invalidates_ && diff --git a/android_webview/browser/in_process_view_renderer.h b/android_webview/browser/in_process_view_renderer.h index fc10f38..8630609 100644 --- a/android_webview/browser/in_process_view_renderer.h +++ b/android_webview/browser/in_process_view_renderer.h @@ -62,6 +62,8 @@ class InProcessViewRenderer : public BrowserViewRenderer, virtual void SetTotalRootLayerScrollOffset( gfx::Vector2dF new_value_css) OVERRIDE; virtual gfx::Vector2dF GetTotalRootLayerScrollOffset() OVERRIDE; + virtual void DidOverscroll(gfx::Vector2dF accumulated_overscroll, + gfx::Vector2dF current_fling_velocity) OVERRIDE; void WebContentsGone(); @@ -115,6 +117,10 @@ class InProcessViewRenderer : public BrowserViewRenderer, // Current scroll offset in CSS pixels. gfx::Vector2dF scroll_offset_css_; + // Used to convert accumulated-based overscroll updates into delta-based + // updates. + gfx::Vector2dF previous_accumulated_overscroll_; + DISALLOW_COPY_AND_ASSIGN(InProcessViewRenderer); }; diff --git a/android_webview/java/src/org/chromium/android_webview/AwContents.java b/android_webview/java/src/org/chromium/android_webview/AwContents.java index d56c24f..ab05434 100644 --- a/android_webview/java/src/org/chromium/android_webview/AwContents.java +++ b/android_webview/java/src/org/chromium/android_webview/AwContents.java @@ -101,6 +101,20 @@ public class AwContents { void onScrollChanged(int lPix, int tPix, int oldlPix, int oldtPix); /** + * @see View#overScrollBy(int, int, int, int, int, int, int, int, boolean); + */ + void overScrollBy(int deltaX, int deltaY, + int scrollX, int scrollY, + int scrollRangeX, int scrollRangeY, + int maxOverScrollX, int maxOverScrollY, + boolean isTouchEvent); + + /** + * @see View#scrollTo(int, int) + */ + void super_scrollTo(int scrollX, int scrollY); + + /** * @see View#setMeasuredDimension(int, int) */ void setMeasuredDimension(int measuredWidth, int measuredHeight); @@ -309,11 +323,27 @@ public class AwContents { } //-------------------------------------------------------------------------------------------- + // NOTE: This content size change notification comes from the compositor and reflects the size + // of the content on screen (but not neccessarily in the renderer main thread). + private class AwContentSizeChangeListener implements ContentViewCore.ContentSizeChangeListener { + @Override + public void onContentSizeChanged(int widthPix, int heightPix) { + mScrollOffsetManager.setContentSize(widthPix, heightPix); + } + } + + //-------------------------------------------------------------------------------------------- private class AwScrollOffsetManagerDelegate implements AwScrollOffsetManager.Delegate { @Override - public boolean scrollContainerViewTo(int x, int y) { - mContainerView.scrollTo(x, y); - return (x == mContainerView.getScrollX() && y == mContainerView.getScrollY()); + public void overScrollContainerViewBy(int deltaX, int deltaY, int scrollX, int scrollY, + int scrollRangeX, int scrollRangeY) { + mInternalAccessAdapter.overScrollBy(deltaX, deltaY, scrollX, scrollY, + scrollRangeX, scrollRangeY, 0, 0, true); + } + + @Override + public void scrollContainerViewTo(int x, int y) { + mInternalAccessAdapter.super_scrollTo(x, y); } @Override @@ -474,6 +504,7 @@ public class AwContents { nativeSetJavaPeers(mNativeAwContents, this, mWebContentsDelegate, mContentsClientBridge, mIoThreadClient, mInterceptNavigationDelegate); mContentsClient.installWebContentsObserver(mContentViewCore); + mContentViewCore.setContentSizeChangeListener(new AwContentSizeChangeListener()); mSettings.setWebContents(nativeWebContents); nativeSetDipScale(mNativeAwContents, (float) mDIPScale); } @@ -588,6 +619,8 @@ public class AwContents { public void onDraw(Canvas canvas) { if (mNativeAwContents == 0) return; + mScrollOffsetManager.syncScrollOffsetFromOnDraw(); + canvas.getClipBounds(mClipBoundsTemporary); if (!nativeOnDraw(mNativeAwContents, canvas, canvas.isHardwareAccelerated(), mContainerView.getScrollX(), mContainerView.getScrollY(), @@ -613,11 +646,21 @@ public class AwContents { /** * Called by the embedder when the scroll offset of the containing view has changed. + * @see View#onScrollChanged(int,int) */ public void onContainerViewScrollChanged(int l, int t, int oldl, int oldt) { mScrollOffsetManager.onContainerViewScrollChanged(l, t); } + /** + * Called by the embedder when the containing view is to be scrolled or overscrolled. + * @see View#onOverScrolled(int,int,int,int) + */ + public void onContainerViewOverScrolled(int scrollX, int scrollY, boolean clampedX, + boolean clampedY) { + mScrollOffsetManager.onContainerViewOverScrolled(scrollX, scrollY, clampedX, clampedY); + } + public Picture capturePicture() { return nativeCapturePicture(mNativeAwContents); } @@ -758,38 +801,38 @@ public class AwContents { } /** - * @see ContentViewCore#computeHorizontalScrollRange() + * @see View#computeHorizontalScrollRange() */ public int computeHorizontalScrollRange() { - return mContentViewCore.computeHorizontalScrollRange(); + return mScrollOffsetManager.computeHorizontalScrollRange(); } /** - * @see ContentViewCore#computeHorizontalScrollOffset() + * @see View#computeHorizontalScrollOffset() */ public int computeHorizontalScrollOffset() { - return mContentViewCore.computeHorizontalScrollOffset(); + return mScrollOffsetManager.computeHorizontalScrollOffset(); } /** - * @see ContentViewCore#computeVerticalScrollRange() + * @see View#computeVerticalScrollRange() */ public int computeVerticalScrollRange() { - return mContentViewCore.computeVerticalScrollRange(); + return mScrollOffsetManager.computeVerticalScrollRange(); } /** - * @see ContentViewCore#computeVerticalScrollOffset() + * @see View#computeVerticalScrollOffset() */ public int computeVerticalScrollOffset() { - return mContentViewCore.computeVerticalScrollOffset(); + return mScrollOffsetManager.computeVerticalScrollOffset(); } /** - * @see ContentViewCore#computeVerticalScrollExtent() + * @see View#computeVerticalScrollExtent() */ public int computeVerticalScrollExtent() { - return mContentViewCore.computeVerticalScrollExtent(); + return mScrollOffsetManager.computeVerticalScrollExtent(); } /** @@ -1214,6 +1257,7 @@ public class AwContents { */ public void onSizeChanged(int w, int h, int ow, int oh) { if (mNativeAwContents == 0) return; + mScrollOffsetManager.setContainerViewSize(w, h); updatePhysicalBackingSizeIfNeeded(); mContentViewCore.onSizeChanged(w, h, ow, oh); nativeOnSizeChanged(mNativeAwContents, w, h, ow, oh); @@ -1503,6 +1547,11 @@ public class AwContents { delegate.init(mContentViewCore, mDIPScale); } + @CalledByNative + private void didOverscroll(int deltaX, int deltaY) { + mScrollOffsetManager.overscrollBy(deltaX, deltaY); + } + // ------------------------------------------------------------------------------------------- // Helper methods // ------------------------------------------------------------------------------------------- diff --git a/android_webview/java/src/org/chromium/android_webview/AwLayoutSizer.java b/android_webview/java/src/org/chromium/android_webview/AwLayoutSizer.java index 7a48bfe..e09259b 100644 --- a/android_webview/java/src/org/chromium/android_webview/AwLayoutSizer.java +++ b/android_webview/java/src/org/chromium/android_webview/AwLayoutSizer.java @@ -60,6 +60,8 @@ public class AwLayoutSizer { /** * This is used to register the AwLayoutSizer to preferred content size change notifications in * the AwWebContentsDelegate. + * NOTE: The preferred size notifications come in from the Renderer main thread and might be + * out of sync with the content size as seen by the InProcessViewRenderer (and Compositor). */ public AwWebContentsDelegateAdapter.PreferredSizeChangedListener getPreferredSizeChangedListener() { diff --git a/android_webview/java/src/org/chromium/android_webview/AwScrollOffsetManager.java b/android_webview/java/src/org/chromium/android_webview/AwScrollOffsetManager.java index c85b079..9d3daf7 100644 --- a/android_webview/java/src/org/chromium/android_webview/AwScrollOffsetManager.java +++ b/android_webview/java/src/org/chromium/android_webview/AwScrollOffsetManager.java @@ -6,42 +6,170 @@ package org.chromium.android_webview; import org.chromium.base.CalledByNative; -class AwScrollOffsetManager { - // The unit of all the values in this delegate are physical pixels +/** + * Takes care of syncing the scroll offset between the Android View system and the + * InProcessViewRenderer. + * + * Unless otherwise values (sizes, scroll offsets) are in physical pixels. + */ +public class AwScrollOffsetManager { + // The unit of all the values in this delegate are physical pixels. public interface Delegate { - // Returns whether the update succeeded - boolean scrollContainerViewTo(int x, int y); + // Call View#overScrollBy on the containerView. + void overScrollContainerViewBy(int deltaX, int deltaY, int scrollX, int scrollY, + int scrollRangeX, int scrollRangeY); + // Call View#scrollTo on the containerView. + void scrollContainerViewTo(int x, int y); + // Store the scroll offset in the native side. This should really be a simple store + // operation, the native side shouldn't synchronously alter the scroll offset from within + // this call. void scrollNativeTo(int x, int y); int getContainerViewScrollX(); int getContainerViewScrollY(); } - final Delegate mDelegate; - int mNativeScrollX; - int mNativeScrollY; + private final Delegate mDelegate; + + // Scroll offset as seen by the native side. + private int mNativeScrollX; + private int mNativeScrollY; + + // Content size. + private int mContentWidth; + private int mContentHeight; + + // Size of the container view. + private int mContainerViewWidth; + private int mContainerViewHeight; public AwScrollOffsetManager(Delegate delegate) { mDelegate = delegate; } + //----- Scroll range and extent calculation methods ------------------------------------------- + + public int computeHorizontalScrollRange() { + return Math.max(mContainerViewWidth, mContentWidth); + } + + public int computeMaximumHorizontalScrollOffset() { + return computeHorizontalScrollRange() - mContainerViewWidth; + } + + public int computeHorizontalScrollOffset() { + return mDelegate.getContainerViewScrollX(); + } + + public int computeVerticalScrollRange() { + return Math.max(mContainerViewHeight, mContentHeight); + } + + public int computeMaximumVerticalScrollOffset() { + return computeVerticalScrollRange() - mContainerViewHeight; + } + + public int computeVerticalScrollOffset() { + return mDelegate.getContainerViewScrollY(); + } + + public int computeVerticalScrollExtent() { + return mContainerViewHeight; + } + + //--------------------------------------------------------------------------------------------- + + // Called when the content size changes. This needs to be the size of the on-screen content and + // therefore we can't use the WebContentsDelegate preferred size. + public void setContentSize(int width, int height) { + mContentWidth = width; + mContentHeight = height; + } + + // Called when the physical size of the view changes. + public void setContainerViewSize(int width, int height) { + mContainerViewWidth = width; + mContainerViewHeight = height; + } + + public void syncScrollOffsetFromOnDraw() { + // Unfortunately apps override onScrollChanged without calling super which is why we need + // to sync the scroll offset on every onDraw. + onContainerViewScrollChanged(mDelegate.getContainerViewScrollX(), + mDelegate.getContainerViewScrollY()); + } + + // Called by the native side to attempt to scroll the container view. public void scrollContainerViewTo(int x, int y) { mNativeScrollX = x; mNativeScrollY = y; - if (!mDelegate.scrollContainerViewTo(x, y)) { - scrollNativeTo(mDelegate.getContainerViewScrollX(), - mDelegate.getContainerViewScrollY()); - } + final int scrollX = mDelegate.getContainerViewScrollX(); + final int scrollY = mDelegate.getContainerViewScrollY(); + final int deltaX = x - scrollX; + final int deltaY = y - scrollY; + final int scrollRangeX = computeMaximumHorizontalScrollOffset(); + final int scrollRangeY = computeMaximumVerticalScrollOffset(); + + // We use overScrollContainerViewBy to be compatible with WebViewClassic which used this + // method for handling both over-scroll as well as in-bounds scroll. + mDelegate.overScrollContainerViewBy(deltaX, deltaY, scrollX, scrollY, + scrollRangeX, scrollRangeY); + } + + // Called by the native side to over-scroll the container view. + public void overscrollBy(int deltaX, int deltaY) { + final int scrollX = mDelegate.getContainerViewScrollX(); + final int scrollY = mDelegate.getContainerViewScrollY(); + final int scrollRangeX = computeMaximumHorizontalScrollOffset(); + final int scrollRangeY = computeMaximumVerticalScrollOffset(); + + mDelegate.overScrollContainerViewBy(deltaX, deltaY, scrollX, scrollY, + scrollRangeX, scrollRangeY); + } + + private int clampHorizontalScroll(int scrollX) { + scrollX = Math.max(0, scrollX); + scrollX = Math.min(computeMaximumHorizontalScrollOffset(), scrollX); + return scrollX; + } + + private int clampVerticalScroll(int scrollY) { + scrollY = Math.max(0, scrollY); + scrollY = Math.min(computeMaximumVerticalScrollOffset(), scrollY); + return scrollY; } + // Called by the View system as a response to the mDelegate.overScrollContainerViewBy call. + public void onContainerViewOverScrolled(int scrollX, int scrollY, boolean clampedX, + boolean clampedY) { + // Clamp the scroll offset at (0, max). + scrollX = clampHorizontalScroll(scrollX); + scrollY = clampVerticalScroll(scrollY); + + mDelegate.scrollContainerViewTo(scrollX, scrollY); + // This will only do anything if the containerView scroll offset ends up being different + // than the one set from native in which case we want the value stored on the native side + // to reflect the value stored in the containerView (and not the other way around). + scrollNativeTo(mDelegate.getContainerViewScrollX(), mDelegate.getContainerViewScrollY()); + } + + // Called by the View system when the scroll offset had changed. This might not get called if + // the embedder overrides WebView#onScrollChanged without calling super.onScrollChanged. If + // this method does get called it is called both as a response to the embedder scrolling the + // view as well as a response to mDelegate.scrollContainerViewTo. public void onContainerViewScrollChanged(int x, int y) { scrollNativeTo(x, y); } private void scrollNativeTo(int x, int y) { + x = clampHorizontalScroll(x); + y = clampVerticalScroll(y); + if (x == mNativeScrollX && y == mNativeScrollY) return; + // The scrollNativeTo call should be a simple store, so it's OK to assume it always + // succeeds. mNativeScrollX = x; mNativeScrollY = y; diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AndroidScrollIntegrationTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AndroidScrollIntegrationTest.java index 0039080..9767f2b 100644 --- a/android_webview/javatests/src/org/chromium/android_webview/test/AndroidScrollIntegrationTest.java +++ b/android_webview/javatests/src/org/chromium/android_webview/test/AndroidScrollIntegrationTest.java @@ -19,6 +19,8 @@ import org.chromium.base.test.util.DisabledTest; import org.chromium.base.test.util.Feature; import org.chromium.content.browser.ContentViewCore; import org.chromium.content.browser.test.util.CallbackHelper; +import org.chromium.content.browser.test.util.Criteria; +import org.chromium.content.browser.test.util.CriteriaHelper; import org.chromium.ui.gfx.DeviceDisplayInfo; import java.util.concurrent.atomic.AtomicBoolean; @@ -27,14 +29,36 @@ import java.util.concurrent.atomic.AtomicBoolean; * Integration tests for synchronous scrolling. */ public class AndroidScrollIntegrationTest extends AwTestBase { + private static final int SCROLL_OFFSET_PROPAGATION_TIMEOUT_MS = 6 * 1000; - public static final int SCROLL_OFFSET_PROPAGATION_TIMEOUT_MS = 6 * 1000; + private static class OverScrollByCallbackHelper extends CallbackHelper { + int mDeltaX; + int mDeltaY; + + public int getDeltaX() { + assert getCallCount() > 0; + return mDeltaX; + } + + public int getDeltaY() { + assert getCallCount() > 0; + return mDeltaY; + } + + public void notifyCalled(int deltaX, int deltaY) { + mDeltaX = deltaX; + mDeltaY = deltaY; + notifyCalled(); + } + } private static class ScrollTestContainerView extends AwTestContainerView { private int mMaxScrollXPix = -1; private int mMaxScrollYPix = -1; private CallbackHelper mOnScrollToCallbackHelper = new CallbackHelper(); + private OverScrollByCallbackHelper mOverScrollByCallbackHelper = + new OverScrollByCallbackHelper(); public ScrollTestContainerView(Context context) { super(context); @@ -44,6 +68,10 @@ public class AndroidScrollIntegrationTest extends AwTestBase { return mOnScrollToCallbackHelper; } + public OverScrollByCallbackHelper getOverScrollByCallbackHelper() { + return mOverScrollByCallbackHelper; + } + public void setMaxScrollX(int maxScrollXPix) { mMaxScrollXPix = maxScrollXPix; } @@ -53,6 +81,15 @@ public class AndroidScrollIntegrationTest extends AwTestBase { } @Override + protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, + int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, + boolean isTouchEvent) { + mOverScrollByCallbackHelper.notifyCalled(deltaX, deltaY); + return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, + scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent); + } + + @Override public void scrollTo(int x, int y) { if (mMaxScrollXPix != -1) x = Math.min(mMaxScrollXPix, x); @@ -154,12 +191,29 @@ public class AndroidScrollIntegrationTest extends AwTestBase { }); } - private void assertScrollInJs(AwContents awContents, TestAwContentsClient contentsClient, - int xCss, int yCss) throws Exception { + private void assertScrollInJs(final AwContents awContents, + final TestAwContentsClient contentsClient, + final int xCss, final int yCss) throws Exception { String x = executeJavaScriptAndWaitForResult(awContents, contentsClient, "window.scrollX"); String y = executeJavaScriptAndWaitForResult(awContents, contentsClient, "window.scrollY"); - assertEquals(Integer.toString(xCss), x); - assertEquals(Integer.toString(yCss), y); + + assertTrue(CriteriaHelper.pollForCriteria(new Criteria() { + @Override + public boolean isSatisfied() { + try { + String x = executeJavaScriptAndWaitForResult(awContents, contentsClient, + "window.scrollX"); + String y = executeJavaScriptAndWaitForResult(awContents, contentsClient, + "window.scrollY"); + return (Integer.toString(xCss).equals(x) && + Integer.toString(yCss).equals(y)); + } catch (Throwable t) { + t.printStackTrace(); + fail("Failed to get window.scroll(X/Y): " + t.toString()); + return false; + } + } + }, WAIT_TIMEOUT_SECONDS * 1000, CHECK_INTERVAL)); } private void loadTestPageAndWaitForFirstFrame(final ScrollTestContainerView testContainerView, @@ -177,10 +231,6 @@ public class AndroidScrollIntegrationTest extends AwTestBase { } }); - // After page load the view's scroll offset will be reset back to (0, 0) at least once. We - // set the initial scroll offset to something different so we can observe that. - scrollToOnMainSync(testContainerView, 10, 10); - loadDataSync(testContainerView.getAwContents(), contentsClient.getOnPageFinishedHelper(), makeTestPage(onscrollObserverName, firstFrameObserverName), "text/html", false); @@ -189,8 +239,6 @@ public class AndroidScrollIntegrationTest extends AwTestBase { // doesn't strictly guarantee that but there isn't a good alternative and this seems to // work fine. firstFrameObserver.waitForEvent(WAIT_TIMEOUT_SECONDS * 1000); - // This is just to double-check that the animation frame waiting code didn't exit too soon. - assertScrollOnMainSync(testContainerView, 0, 0); } @SmallTest @@ -339,4 +387,63 @@ public class AndroidScrollIntegrationTest extends AwTestBase { assertScrollInJs(testContainerView.getAwContents(), contentsClient, maxScrollXCss, maxScrollYCss); } + + @SmallTest + @Feature({"AndroidWebView"}) + public void testOverScrollX() throws Throwable { + final TestAwContentsClient contentsClient = new TestAwContentsClient(); + final ScrollTestContainerView testContainerView = + (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient); + final OverScrollByCallbackHelper overScrollByCallbackHelper = + testContainerView.getOverScrollByCallbackHelper(); + enableJavaScriptOnUiThread(testContainerView.getAwContents()); + + final int overScrollDeltaX = 30; + final int oneStep = 1; + + loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null); + + // Scroll separately in different dimensions because of vertical/horizontal scroll + // snap. + final int overScrollCallCount = overScrollByCallbackHelper.getCallCount(); + AwTestTouchUtils.dragCompleteView(testContainerView, + 0, overScrollDeltaX, + 0, 0, + oneStep); + overScrollByCallbackHelper.waitForCallback(overScrollCallCount); + // Unfortunately the gesture detector seems to 'eat' some number of pixels. For now + // checking that the value is < 0 (overscroll is reported as negative values) will have to + // do. + assertTrue(0 > overScrollByCallbackHelper.getDeltaX()); + assertEquals(0, overScrollByCallbackHelper.getDeltaY()); + + assertScrollOnMainSync(testContainerView, 0, 0); + } + + @SmallTest + @Feature({"AndroidWebView"}) + public void testOverScrollY() throws Throwable { + final TestAwContentsClient contentsClient = new TestAwContentsClient(); + final ScrollTestContainerView testContainerView = + (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient); + final OverScrollByCallbackHelper overScrollByCallbackHelper = + testContainerView.getOverScrollByCallbackHelper(); + enableJavaScriptOnUiThread(testContainerView.getAwContents()); + + final int overScrollDeltaY = 30; + final int oneStep = 1; + + loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null); + + int overScrollCallCount = overScrollByCallbackHelper.getCallCount(); + AwTestTouchUtils.dragCompleteView(testContainerView, + 0, 0, + 0, overScrollDeltaY, + oneStep); + overScrollByCallbackHelper.waitForCallback(overScrollCallCount); + assertEquals(0, overScrollByCallbackHelper.getDeltaX()); + assertTrue(0 > overScrollByCallbackHelper.getDeltaY()); + + assertScrollOnMainSync(testContainerView, 0, 0); + } } diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwScrollOffsetManagerTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwScrollOffsetManagerTest.java new file mode 100644 index 0000000..b29d606 --- /dev/null +++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwScrollOffsetManagerTest.java @@ -0,0 +1,278 @@ +// Copyright 2013 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.android_webview.test; + +import android.view.View; +import android.view.View.MeasureSpec; +import android.test.InstrumentationTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import org.chromium.android_webview.AwScrollOffsetManager; + +public class AwScrollOffsetManagerTest extends InstrumentationTestCase { + private static class TestScrollOffsetManagerDelegate implements AwScrollOffsetManager.Delegate { + private int mOverScrollDeltaX; + private int mOverScrollDeltaY; + private int mOverScrollCallCount; + private int mScrollX; + private int mScrollY; + private int mNativeScrollX; + private int mNativeScrollY; + + public int getOverScrollDeltaX() { + return mOverScrollDeltaX; + } + + public int getOverScrollDeltaY() { + return mOverScrollDeltaY; + } + + public int getOverScrollCallCount() { + return mOverScrollCallCount; + } + + public int getScrollX() { + return mScrollX; + } + + public int getScrollY() { + return mScrollY; + } + + public int getNativeScrollX() { + return mNativeScrollX; + } + + public int getNativeScrollY() { + return mNativeScrollY; + } + + @Override + public void overScrollContainerViewBy(int deltaX, int deltaY, int scrollX, int scrollY, + int scrollRangeX, int scrollRangeY) { + mOverScrollDeltaX = deltaX; + mOverScrollDeltaY = deltaY; + mOverScrollCallCount += 1; + } + + @Override + public void scrollContainerViewTo(int x, int y) { + mScrollX = x; + mScrollY = y; + } + + @Override + public void scrollNativeTo(int x, int y) { + mNativeScrollX = x; + mNativeScrollY = y; + } + + @Override + public int getContainerViewScrollX() { + return mScrollX; + } + + @Override + public int getContainerViewScrollY() { + return mScrollY; + } + } + + private void simulateScrolling(AwScrollOffsetManager offsetManager, + TestScrollOffsetManagerDelegate delegate, int scrollX, int scrollY) { + // Scrolling is a two-phase action. First we ask the manager to scroll + int callCount = delegate.getOverScrollCallCount(); + offsetManager.scrollContainerViewTo(scrollX, scrollY); + // The manager then asks the delegate to overscroll the view. + assertEquals(callCount + 1, delegate.getOverScrollCallCount()); + assertEquals(scrollX, delegate.getOverScrollDeltaX() + delegate.getScrollX()); + assertEquals(scrollY, delegate.getOverScrollDeltaY() + delegate.getScrollY()); + // As a response to that the menager expects the view to call back with the new scroll. + offsetManager.onContainerViewOverScrolled(scrollX, scrollY, false, false); + } + + public void testWhenContentSizeMatchesView() { + TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate(); + AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate); + + final int width = 132; + final int height = 212; + final int scrollX = 11; + final int scrollY = 13; + + offsetManager.setContentSize(width, height); + offsetManager.setContainerViewSize(width, height); + + assertEquals(width, offsetManager.computeHorizontalScrollRange()); + assertEquals(height, offsetManager.computeVerticalScrollRange()); + + // Since the view size and contents size are equal no scrolling should be possible. + assertEquals(0, offsetManager.computeMaximumHorizontalScrollOffset()); + assertEquals(0, offsetManager.computeMaximumVerticalScrollOffset()); + + // Scrolling should generate overscroll but not update the scroll offset. + simulateScrolling(offsetManager, delegate, scrollX, scrollY); + assertEquals(scrollX, delegate.getOverScrollDeltaX()); + assertEquals(scrollY, delegate.getOverScrollDeltaY()); + assertEquals(0, delegate.getScrollX()); + assertEquals(0, delegate.getScrollY()); + assertEquals(0, delegate.getNativeScrollX()); + assertEquals(0, delegate.getNativeScrollY()); + + // Scrolling to 0,0 should result in no deltas. + simulateScrolling(offsetManager, delegate, 0, 0); + assertEquals(0, delegate.getOverScrollDeltaX()); + assertEquals(0, delegate.getOverScrollDeltaY()); + + // Negative scrolling should result in negative deltas but no scroll offset update. + simulateScrolling(offsetManager, delegate, -scrollX, -scrollY); + assertEquals(-scrollX, delegate.getOverScrollDeltaX()); + assertEquals(-scrollY, delegate.getOverScrollDeltaY()); + assertEquals(0, delegate.getScrollX()); + assertEquals(0, delegate.getScrollY()); + assertEquals(0, delegate.getNativeScrollX()); + assertEquals(0, delegate.getNativeScrollY()); + } + + private static final int VIEW_WIDTH = 211; + private static final int VIEW_HEIGHT = 312; + private static final int MAX_HORIZONTAL_OFFSET = 61; + private static final int MAX_VERTICAL_OFFSET = 42; + private static final int CONTENT_WIDTH = VIEW_WIDTH + MAX_HORIZONTAL_OFFSET; + private static final int CONTENT_HEIGHT = VIEW_HEIGHT + MAX_VERTICAL_OFFSET; + + public void testScrollRangeAndMaxOffset() { + TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate(); + AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate); + + offsetManager.setContentSize(CONTENT_WIDTH, CONTENT_HEIGHT); + offsetManager.setContainerViewSize(VIEW_WIDTH, VIEW_HEIGHT); + + assertEquals(CONTENT_WIDTH, offsetManager.computeHorizontalScrollRange()); + assertEquals(CONTENT_HEIGHT, offsetManager.computeVerticalScrollRange()); + + assertEquals(MAX_HORIZONTAL_OFFSET, offsetManager.computeMaximumHorizontalScrollOffset()); + assertEquals(MAX_VERTICAL_OFFSET, offsetManager.computeMaximumVerticalScrollOffset()); + + // Scrolling beyond the maximum should be clamped. + final int scrollX = MAX_HORIZONTAL_OFFSET + 10; + final int scrollY = MAX_VERTICAL_OFFSET + 11; + + simulateScrolling(offsetManager, delegate, scrollX, scrollY); + assertEquals(scrollX, delegate.getOverScrollDeltaX()); + assertEquals(scrollY, delegate.getOverScrollDeltaY()); + assertEquals(MAX_HORIZONTAL_OFFSET, delegate.getScrollX()); + assertEquals(MAX_VERTICAL_OFFSET, delegate.getScrollY()); + assertEquals(MAX_HORIZONTAL_OFFSET, delegate.getNativeScrollX()); + assertEquals(MAX_VERTICAL_OFFSET, delegate.getNativeScrollY()); + + // Scrolling to negative coordinates should be clamped back to 0,0. + simulateScrolling(offsetManager, delegate, -scrollX, -scrollY); + assertEquals(0, delegate.getScrollX()); + assertEquals(0, delegate.getScrollY()); + assertEquals(0, delegate.getNativeScrollX()); + assertEquals(0, delegate.getNativeScrollY()); + + // The onScrollChanged method is callable by third party code and should also be clamped + offsetManager.onContainerViewScrollChanged(scrollX, scrollY); + assertEquals(MAX_HORIZONTAL_OFFSET, delegate.getNativeScrollX()); + assertEquals(MAX_VERTICAL_OFFSET, delegate.getNativeScrollY()); + + offsetManager.onContainerViewScrollChanged(-scrollX, -scrollY); + assertEquals(0, delegate.getNativeScrollX()); + assertEquals(0, delegate.getNativeScrollY()); + } + + public void testDelegateCanOverrideScroll() { + final int overrideScrollX = 10; + final int overrideScrollY = 10; + + TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate() { + @Override + public int getContainerViewScrollX() { + return overrideScrollX; + } + + @Override + public int getContainerViewScrollY() { + return overrideScrollY; + } + }; + AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate); + + offsetManager.setContentSize(CONTENT_WIDTH, CONTENT_HEIGHT); + offsetManager.setContainerViewSize(VIEW_WIDTH, VIEW_HEIGHT); + + offsetManager.onContainerViewOverScrolled(0, 0, false, false); + assertEquals(overrideScrollX, delegate.getNativeScrollX()); + assertEquals(overrideScrollY, delegate.getNativeScrollY()); + } + + public void testDelegateOverridenScrollsDontExceedBounds() { + final int overrideScrollX = 222; + final int overrideScrollY = 333; + TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate() { + @Override + public int getContainerViewScrollX() { + return overrideScrollX; + } + + @Override + public int getContainerViewScrollY() { + return overrideScrollY; + } + }; + AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate); + + offsetManager.setContentSize(CONTENT_WIDTH, CONTENT_HEIGHT); + offsetManager.setContainerViewSize(VIEW_WIDTH, VIEW_HEIGHT); + + offsetManager.onContainerViewOverScrolled(0, 0, false, false); + assertEquals(MAX_HORIZONTAL_OFFSET, delegate.getNativeScrollX()); + assertEquals(MAX_VERTICAL_OFFSET, delegate.getNativeScrollY()); + } + + public void testScrollContainerViewTo() { + TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate(); + AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate); + + final int scrollX = 31; + final int scrollY = 41; + + offsetManager.setContentSize(CONTENT_WIDTH, CONTENT_HEIGHT); + offsetManager.setContainerViewSize(VIEW_WIDTH, VIEW_HEIGHT); + + assertEquals(0, delegate.getOverScrollDeltaX()); + assertEquals(0, delegate.getOverScrollDeltaY()); + int callCount = delegate.getOverScrollCallCount(); + + offsetManager.scrollContainerViewTo(scrollX, scrollY); + assertEquals(callCount + 1, delegate.getOverScrollCallCount()); + assertEquals(scrollX, delegate.getOverScrollDeltaX()); + assertEquals(scrollY, delegate.getOverScrollDeltaY()); + } + + public void testOnContainerViewOverScrolled() { + TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate(); + AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate); + + final int scrollX = 31; + final int scrollY = 41; + + offsetManager.setContentSize(CONTENT_WIDTH, CONTENT_HEIGHT); + offsetManager.setContainerViewSize(VIEW_WIDTH, VIEW_HEIGHT); + + assertEquals(0, delegate.getScrollX()); + assertEquals(0, delegate.getScrollY()); + assertEquals(0, delegate.getNativeScrollX()); + assertEquals(0, delegate.getNativeScrollY()); + + offsetManager.onContainerViewOverScrolled(scrollX, scrollY, false, false); + assertEquals(scrollX, delegate.getScrollX()); + assertEquals(scrollY, delegate.getScrollY()); + assertEquals(scrollX, delegate.getNativeScrollX()); + assertEquals(scrollY, delegate.getNativeScrollY()); + } +} diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwTestBase.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwTestBase.java index da078d2..0bb148e 100644 --- a/android_webview/javatests/src/org/chromium/android_webview/test/AwTestBase.java +++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwTestBase.java @@ -32,8 +32,8 @@ import java.util.concurrent.atomic.AtomicReference; */ public class AwTestBase extends ActivityInstrumentationTestCase2<AwTestRunnerActivity> { - protected final static int WAIT_TIMEOUT_SECONDS = 15; - private static final int CHECK_INTERVAL = 100; + protected static final int WAIT_TIMEOUT_SECONDS = 15; + protected static final int CHECK_INTERVAL = 100; public AwTestBase() { super(AwTestRunnerActivity.class); diff --git a/android_webview/native/aw_contents.cc b/android_webview/native/aw_contents.cc index f87a8dd..357e072 100644 --- a/android_webview/native/aw_contents.cc +++ b/android_webview/native/aw_contents.cc @@ -701,6 +701,15 @@ void AwContents::ScrollContainerViewTo(gfx::Vector2d new_value) { } +void AwContents::DidOverscroll(gfx::Vector2d overscroll_delta) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jobject> obj = java_ref_.get(env); + if (obj.is_null()) + return; + Java_AwContents_didOverscroll( + env, obj.obj(), overscroll_delta.x(), overscroll_delta.y()); +} + void AwContents::SetDipScale(JNIEnv* env, jobject obj, jfloat dipScale) { browser_view_renderer_->SetDipScale(dipScale); } diff --git a/android_webview/native/aw_contents.h b/android_webview/native/aw_contents.h index 40e3551..7a44f4f 100644 --- a/android_webview/native/aw_contents.h +++ b/android_webview/native/aw_contents.h @@ -148,6 +148,7 @@ class AwContents : public FindHelper::Listener, virtual void OnNewPicture() OVERRIDE; virtual gfx::Point GetLocationOnScreen() OVERRIDE; virtual void ScrollContainerViewTo(gfx::Vector2d new_value) OVERRIDE; + virtual void DidOverscroll(gfx::Vector2d overscroll_delta) OVERRIDE; void ClearCache(JNIEnv* env, jobject obj, jboolean include_disk_files); void SetPendingWebContentsForPopup(scoped_ptr<content::WebContents> pending); diff --git a/android_webview/test/shell/src/org/chromium/android_webview/test/AwTestContainerView.java b/android_webview/test/shell/src/org/chromium/android_webview/test/AwTestContainerView.java index f3a3e88..3860484 100644 --- a/android_webview/test/shell/src/org/chromium/android_webview/test/AwTestContainerView.java +++ b/android_webview/test/shell/src/org/chromium/android_webview/test/AwTestContainerView.java @@ -32,6 +32,7 @@ public class AwTestContainerView extends FrameLayout { public AwTestContainerView(Context context) { super(context); mInternalAccessDelegate = new InternalAccessAdapter(); + setOverScrollMode(View.OVER_SCROLL_ALWAYS); } public void initialize(AwContents awContents) { @@ -105,6 +106,11 @@ public class AwTestContainerView extends FrameLayout { } @Override + public void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) { + mAwContents.onContainerViewOverScrolled(scrollX, scrollY, clampedX, clampedY); + } + + @Override public void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); if (mAwContents != null) { @@ -171,6 +177,23 @@ public class AwTestContainerView extends FrameLayout { } @Override + public void super_scrollTo(int scrollX, int scrollY) { + // We're intentionally not calling super.scrollTo here to make testing easier. + AwTestContainerView.this.scrollTo(scrollX, scrollY); + } + + @Override + public void overScrollBy(int deltaX, int deltaY, + int scrollX, int scrollY, + int scrollRangeX, int scrollRangeY, + int maxOverScrollX, int maxOverScrollY, + boolean isTouchEvent) { + // We're intentionally not calling super.scrollTo here to make testing easier. + AwTestContainerView.this.overScrollBy(deltaX, deltaY, scrollX, scrollY, + scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent); + } + + @Override public void onScrollChanged(int l, int t, int oldl, int oldt) { AwTestContainerView.super.onScrollChanged(l, t, oldl, oldt); } |