summaryrefslogtreecommitdiffstats
path: root/android_webview
diff options
context:
space:
mode:
Diffstat (limited to 'android_webview')
-rw-r--r--android_webview/browser/browser_view_renderer.h3
-rw-r--r--android_webview/browser/in_process_view_renderer.cc14
-rw-r--r--android_webview/browser/in_process_view_renderer.h6
-rw-r--r--android_webview/java/src/org/chromium/android_webview/AwContents.java75
-rw-r--r--android_webview/java/src/org/chromium/android_webview/AwLayoutSizer.java2
-rw-r--r--android_webview/java/src/org/chromium/android_webview/AwScrollOffsetManager.java150
-rw-r--r--android_webview/javatests/src/org/chromium/android_webview/test/AndroidScrollIntegrationTest.java129
-rw-r--r--android_webview/javatests/src/org/chromium/android_webview/test/AwScrollOffsetManagerTest.java278
-rw-r--r--android_webview/javatests/src/org/chromium/android_webview/test/AwTestBase.java4
-rw-r--r--android_webview/native/aw_contents.cc9
-rw-r--r--android_webview/native/aw_contents.h1
-rw-r--r--android_webview/test/shell/src/org/chromium/android_webview/test/AwTestContainerView.java23
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);
}