diff options
author | mkosiba@chromium.org <mkosiba@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-06-20 22:40:30 +0000 |
---|---|---|
committer | mkosiba@chromium.org <mkosiba@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-06-20 22:40:30 +0000 |
commit | 36a2df7239f2fb104d77981f7edb0ecff3d8c84d (patch) | |
tree | 616812c3f5398caf0af122e838a268bab6d00671 | |
parent | 4e40d83a011b8651f7f7f1da33dff5a37af93238 (diff) | |
download | chromium_src-36a2df7239f2fb104d77981f7edb0ecff3d8c84d.zip chromium_src-36a2df7239f2fb104d77981f7edb0ecff3d8c84d.tar.gz chromium_src-36a2df7239f2fb104d77981f7edb0ecff3d8c84d.tar.bz2 |
Hookup android_webview scroll offset delegation to Java side.
This connects the scroll offset that is delegated from the
CC all the way up to the Java android.view.View class.
BUG=b/6029133
TEST=AndroidWebViewTest
Review URL: https://chromiumcodereview.appspot.com/16255010
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@207614 0039d316-1c4b-4281-b951-d872f2087c98
15 files changed, 576 insertions, 63 deletions
diff --git a/android_webview/browser/browser_view_renderer.h b/android_webview/browser/browser_view_renderer.h index 52f917e..dad70ed 100644 --- a/android_webview/browser/browser_view_renderer.h +++ b/android_webview/browser/browser_view_renderer.h @@ -8,6 +8,7 @@ #include "base/android/scoped_java_ref.h" #include "ui/gfx/point.h" #include "ui/gfx/rect.h" +#include "ui/gfx/vector2d_f.h" struct AwDrawGLInfo; struct AwDrawSWFunctionTable; @@ -16,10 +17,6 @@ namespace content { class ContentViewCore; } -namespace gfx { -class Rect; -} - namespace android_webview { // Interface for all the WebView-specific content rendering operations. @@ -43,6 +40,9 @@ class BrowserViewRenderer { // Called to get view's absolute location on the screen. virtual gfx::Point GetLocationOnScreen() = 0; + // Try to set the view's scroll offset to |new_value|. + virtual void ScrollContainerViewTo(gfx::Vector2d new_value) = 0; + protected: virtual ~Client() {} }; @@ -88,7 +88,7 @@ class BrowserViewRenderer { // scroll offset. |clip| is the canvas's clip bounds. virtual bool OnDraw(jobject java_canvas, bool is_hardware_canvas, - const gfx::Point& scroll, + const gfx::Vector2d& scroll, const gfx::Rect& clip) = 0; // Called in response to a prior Client::RequestDrawGL() call. See // AwDrawGLInfo documentation for more details of the contract. @@ -104,6 +104,12 @@ class BrowserViewRenderer { virtual void OnAttachedToWindow(int width, int height) = 0; virtual void OnDetachedFromWindow() = 0; + // Sets the scale for logical<->physical pixel conversions. + virtual void SetDipScale(float dip_scale) = 0; + + // Set the root layer scroll offset to |new_value|. + virtual void ScrollTo(gfx::Vector2d new_value) = 0; + // Android views hierarchy gluing. virtual bool IsAttachedToWindow() = 0; virtual bool IsViewVisible() = 0; diff --git a/android_webview/browser/in_process_view_renderer.cc b/android_webview/browser/in_process_view_renderer.cc index 28810e2..acb6db8 100644 --- a/android_webview/browser/in_process_view_renderer.cc +++ b/android_webview/browser/in_process_view_renderer.cc @@ -25,6 +25,7 @@ #include "ui/gfx/size_conversions.h" #include "ui/gfx/skia_util.h" #include "ui/gfx/transform.h" +#include "ui/gfx/vector2d_conversions.h" #include "ui/gfx/vector2d_f.h" #include "ui/gl/gl_bindings.h" @@ -306,6 +307,7 @@ InProcessViewRenderer::InProcessViewRenderer( web_contents_(web_contents), compositor_(NULL), view_visible_(false), + dip_scale_(0.0), continuous_invalidate_(false), block_invalidates_(false), width_(0), @@ -341,7 +343,7 @@ void InProcessViewRenderer::WebContentsGone() { bool InProcessViewRenderer::OnDraw(jobject java_canvas, bool is_hardware_canvas, - const gfx::Point& scroll, + const gfx::Vector2d& scroll, const gfx::Rect& clip) { scroll_at_start_of_frame_ = scroll; if (is_hardware_canvas && attached_to_window_ && compositor_ && @@ -643,14 +645,49 @@ void InProcessViewRenderer::SetContinuousInvalidate(bool invalidate) { EnsureContinuousInvalidation(NULL); } +void InProcessViewRenderer::SetDipScale(float dip_scale) { + dip_scale_ = dip_scale; + CHECK(dip_scale_ > 0); +} + +void InProcessViewRenderer::ScrollTo(gfx::Vector2d new_value) { + DCHECK(dip_scale_ > 0); + // In general we don't guarantee that the scroll offset transforms are + // symmetrical. That is if scrolling from JS to offset1 results in a native + // offset2 then scrolling from UI to offset2 results in JS being scrolled to + // offset1 again. + // The reason we explicitly do rounding here is that it seems to yeld the + // most stabile transformation. + gfx::Vector2dF new_value_css = gfx::ToRoundedVector2d( + gfx::ScaleVector2d(new_value, 1.0f / dip_scale_)); + + DCHECK(scroll_offset_css_ != new_value_css); + + scroll_offset_css_ = new_value_css; + + if (compositor_) + compositor_->DidChangeRootLayerScrollOffset(); +} + void InProcessViewRenderer::SetTotalRootLayerScrollOffset( - gfx::Vector2dF new_value) { - // TODO(mkosiba): Plumb this all the way through to the view. - scroll_offset_ = new_value; + gfx::Vector2dF new_value_css) { + // 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) + return; + + scroll_offset_css_ = new_value_css; + + DCHECK(dip_scale_ > 0); + + gfx::Vector2d scroll_offset = + gfx::ToRoundedVector2d(gfx::ScaleVector2d(new_value_css, dip_scale_)); + + client_->ScrollContainerViewTo(scroll_offset); } gfx::Vector2dF InProcessViewRenderer::GetTotalRootLayerScrollOffset() { - return scroll_offset_; + return scroll_offset_css_; } void InProcessViewRenderer::EnsureContinuousInvalidation( diff --git a/android_webview/browser/in_process_view_renderer.h b/android_webview/browser/in_process_view_renderer.h index 73262bf..db85926 100644 --- a/android_webview/browser/in_process_view_renderer.h +++ b/android_webview/browser/in_process_view_renderer.h @@ -9,6 +9,7 @@ #include "base/memory/weak_ptr.h" #include "content/public/browser/android/synchronous_compositor_client.h" +#include "ui/gfx/vector2d_f.h" namespace content { class SynchronousCompositor; @@ -36,7 +37,7 @@ class InProcessViewRenderer : public BrowserViewRenderer, // BrowserViewRenderer overrides virtual bool OnDraw(jobject java_canvas, bool is_hardware_canvas, - const gfx::Point& scroll, + const gfx::Vector2d& scroll_, const gfx::Rect& clip) OVERRIDE; virtual void DrawGL(AwDrawGLInfo* draw_info) OVERRIDE; virtual base::android::ScopedJavaLocalRef<jobject> CapturePicture() OVERRIDE; @@ -44,8 +45,10 @@ class InProcessViewRenderer : public BrowserViewRenderer, virtual void OnVisibilityChanged( bool view_visible, bool window_visible) OVERRIDE; virtual void OnSizeChanged(int width, int height) OVERRIDE; + virtual void ScrollTo(gfx::Vector2d new_value) OVERRIDE; virtual void OnAttachedToWindow(int width, int height) OVERRIDE; virtual void OnDetachedFromWindow() OVERRIDE; + virtual void SetDipScale(float dip_scale) OVERRIDE; virtual bool IsAttachedToWindow() OVERRIDE; virtual bool IsViewVisible() OVERRIDE; virtual gfx::Rect GetScreenRect() OVERRIDE; @@ -56,7 +59,8 @@ class InProcessViewRenderer : public BrowserViewRenderer, virtual void DidDestroyCompositor( content::SynchronousCompositor* compositor) OVERRIDE; virtual void SetContinuousInvalidate(bool invalidate) OVERRIDE; - virtual void SetTotalRootLayerScrollOffset(gfx::Vector2dF new_value) OVERRIDE; + virtual void SetTotalRootLayerScrollOffset( + gfx::Vector2dF new_value_css) OVERRIDE; virtual gfx::Vector2dF GetTotalRootLayerScrollOffset() OVERRIDE; void WebContentsGone(); @@ -74,6 +78,7 @@ class InProcessViewRenderer : public BrowserViewRenderer, content::SynchronousCompositor* compositor_; bool view_visible_; + float dip_scale_; // When true, we should continuously invalidate and keep drawing, for example // to drive animation. @@ -95,9 +100,10 @@ class InProcessViewRenderer : public BrowserViewRenderer, EGLContext last_egl_context_; // Last View scroll when View.onDraw() was called. - gfx::Point scroll_at_start_of_frame_; + gfx::Vector2d scroll_at_start_of_frame_; - gfx::Vector2dF scroll_offset_; + // Current scroll offset in CSS pixels. + gfx::Vector2dF scroll_offset_css_; 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 cec63c2..4f8b72b 100644 --- a/android_webview/java/src/org/chromium/android_webview/AwContents.java +++ b/android_webview/java/src/org/chromium/android_webview/AwContents.java @@ -92,6 +92,14 @@ public class AwContents { */ public interface InternalAccessDelegate extends ContentViewCore.InternalAccessDelegate { /** + * @see View#onScrollChanged(int, int, int, int) + * + * TODO(mkosiba): WebViewClassic calls this, AwContents doesn't. Check if there + * are any cases we're missing, if not - remove. + */ + void onScrollChanged(int lPix, int tPix, int oldlPix, int oldtPix); + + /** * @see View#setMeasuredDimension(int, int) */ void setMeasuredDimension(int measuredWidth, int measuredHeight); @@ -119,6 +127,7 @@ public class AwContents { private final InterceptNavigationDelegateImpl mInterceptNavigationDelegate; private final InternalAccessDelegate mInternalAccessAdapter; private final AwLayoutSizer mLayoutSizer; + private final AwScrollOffsetManager mScrollOffsetManager; private final AwZoomControls mZoomControls; // This can be accessed on any thread after construction. See AwContentsIoThreadClient. private final AwSettings mSettings; @@ -280,6 +289,30 @@ public class AwContents { } //-------------------------------------------------------------------------------------------- + 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()); + } + + @Override + public void scrollNativeTo(int x, int y) { + nativeScrollTo(mNativeAwContents, x, y); + } + + @Override + public int getContainerViewScrollX() { + return mContainerView.getScrollX(); + } + + @Override + public int getContainerViewScrollY() { + return mContainerView.getScrollY(); + } + } + + //-------------------------------------------------------------------------------------------- private class AwPinchGestureStateListener implements ContentViewCore.PinchGestureStateListener { @Override public void onPinchGestureStart() { @@ -384,6 +417,7 @@ public class AwContents { mSettings.setDefaultVideoPosterURL( mDefaultVideoPosterRequestHandler.getDefaultVideoPosterURL()); mContentsClient.setDIPScale(mDIPScale); + mScrollOffsetManager = new AwScrollOffsetManager(new AwScrollOffsetManagerDelegate()); setNewAwContents(nativeInit(browserContext)); } @@ -421,6 +455,7 @@ public class AwContents { mIoThreadClient, mInterceptNavigationDelegate); mContentsClient.installWebContentsObserver(mContentViewCore); mSettings.setWebContents(nativeWebContents); + nativeSetDipScale(mNativeAwContents, (float) mDIPScale); } /** @@ -556,6 +591,13 @@ public class AwContents { return (int) Math.ceil(mContentViewCore.getContentWidthCss()); } + /** + * Called by the embedder when the scroll offset of the containing view has changed. + */ + public void onContainerViewScrollChanged(int l, int t, int oldl, int oldt) { + mScrollOffsetManager.onContainerViewScrollChanged(l, t); + } + public Picture capturePicture() { return nativeCapturePicture(mNativeAwContents); } @@ -1398,6 +1440,11 @@ public class AwContents { mLayoutSizer.onPageScaleChanged(pageScaleFactor); } + @CalledByNative + private void scrollContainerViewTo(int x, int y) { + mScrollOffsetManager.scrollContainerViewTo(x, y); + } + // ------------------------------------------------------------------------------------------- // Helper methods // ------------------------------------------------------------------------------------------- @@ -1485,10 +1532,12 @@ public class AwContents { private native void nativeUpdateLastHitTestData(int nativeAwContents); private native void nativeOnSizeChanged(int nativeAwContents, int w, int h, int ow, int oh); + private native void nativeScrollTo(int nativeAwContents, int x, int y); private native void nativeSetWindowViewVisibility(int nativeAwContents, boolean windowVisible, boolean viewVisible); private native void nativeOnAttachedToWindow(int nativeAwContents, int w, int h); private native void nativeOnDetachedFromWindow(int nativeAwContents); + private native void nativeSetDipScale(int nativeAwContents, float dipScale); // Returns null if save state fails. private native byte[] nativeGetOpaqueState(int nativeAwContents); diff --git a/android_webview/java/src/org/chromium/android_webview/AwScrollOffsetManager.java b/android_webview/java/src/org/chromium/android_webview/AwScrollOffsetManager.java new file mode 100644 index 0000000..c85b079 --- /dev/null +++ b/android_webview/java/src/org/chromium/android_webview/AwScrollOffsetManager.java @@ -0,0 +1,50 @@ +// 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; + +import org.chromium.base.CalledByNative; + +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); + void scrollNativeTo(int x, int y); + int getContainerViewScrollX(); + int getContainerViewScrollY(); + } + + final Delegate mDelegate; + int mNativeScrollX; + int mNativeScrollY; + + public AwScrollOffsetManager(Delegate delegate) { + mDelegate = delegate; + } + + public void scrollContainerViewTo(int x, int y) { + mNativeScrollX = x; + mNativeScrollY = y; + + if (!mDelegate.scrollContainerViewTo(x, y)) { + scrollNativeTo(mDelegate.getContainerViewScrollX(), + mDelegate.getContainerViewScrollY()); + } + } + + public void onContainerViewScrollChanged(int x, int y) { + scrollNativeTo(x, y); + } + + private void scrollNativeTo(int x, int y) { + if (x == mNativeScrollX && y == mNativeScrollY) + return; + + mNativeScrollX = x; + mNativeScrollY = y; + + mDelegate.scrollNativeTo(x, 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 new file mode 100644 index 0000000..f57acc0 --- /dev/null +++ b/android_webview/javatests/src/org/chromium/android_webview/test/AndroidScrollIntegrationTest.java @@ -0,0 +1,349 @@ +// 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.content.Context; +import android.test.suitebuilder.annotation.SmallTest; +import android.util.Log; +import android.view.Gravity; +import android.view.View; + +import org.chromium.android_webview.AwContents; +import org.chromium.android_webview.AwContentsClient; +import org.chromium.android_webview.test.util.CommonResources; +import org.chromium.android_webview.test.util.JavascriptEventObserver; +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.CallbackHelper; +import org.chromium.content.browser.test.util.TestTouchUtils; +import org.chromium.ui.gfx.DeviceDisplayInfo; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Integration tests for synchronous scrolling. + */ +public class AndroidScrollIntegrationTest extends AwTestBase { + + public static final int SCROLL_OFFSET_PROPAGATION_TIMEOUT_MS = 6 * 1000; + + private static class ScrollTestContainerView extends AwTestContainerView { + private int mMaxScrollXPix = -1; + private int mMaxScrollYPix = -1; + + private CallbackHelper mOnScrollToCallbackHelper = new CallbackHelper(); + + public ScrollTestContainerView(Context context) { + super(context); + } + + public CallbackHelper getOnScrollToCallbackHelper() { + return mOnScrollToCallbackHelper; + } + + public void setMaxScrollX(int maxScrollXPix) { + mMaxScrollXPix = maxScrollXPix; + } + + public void setMaxScrollY(int maxScrollYPix) { + mMaxScrollYPix = maxScrollYPix; + } + + @Override + public void scrollTo(int x, int y) { + if (mMaxScrollXPix != -1) + x = Math.min(mMaxScrollXPix, x); + if (mMaxScrollYPix != -1) + y = Math.min(mMaxScrollYPix, y); + super.scrollTo(x, y); + mOnScrollToCallbackHelper.notifyCalled(); + } + } + + @Override + protected TestDependencyFactory createTestDependencyFactory() { + return new TestDependencyFactory() { + @Override + public AwTestContainerView createAwTestContainerView(AwTestRunnerActivity activity) { + return new ScrollTestContainerView(activity); + } + }; + } + + private String makeTestPage(String onscrollObserver, String firstFrameObserver) { + String headers = + "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"> " + + "<style type=\"text/css\"> " + + " div { " + + " width:1000px; " + + " height:10000px; " + + " background-color: blue; " + + " } " + + "</style> "; + String content = "<div>test div</div> "; + if (onscrollObserver != null) { + content += + "<script> " + + " window.onscroll = function(oEvent) { " + + " " + onscrollObserver + ".notifyJava(); " + + " } " + + "</script>"; + } + if (firstFrameObserver != null) { + content += + "<script> " + + " window.framesToIgnore = 10; " + + " window.onAnimationFrame = function(timestamp) { " + + " if (window.framesToIgnore == 0) { " + + " " + firstFrameObserver + ".notifyJava(); " + + " } else {" + + " window.framesToIgnore -= 1; " + + " window.requestAnimationFrame(window.onAnimationFrame); " + + " } " + + " }; " + + " window.requestAnimationFrame(window.onAnimationFrame); " + + "</script>"; + } + return CommonResources.makeHtmlPageFrom(headers, content); + } + + private void scrollToOnMainSync(final View view, final int xPix, final int yPix) { + getInstrumentation().runOnMainSync(new Runnable() { + @Override + public void run() { + view.scrollTo(xPix, yPix); + } + }); + } + + private void setMaxScrollOnMainSync(final ScrollTestContainerView testContainerView, + final int maxScrollXPix, final int maxScrollYPix) { + getInstrumentation().runOnMainSync(new Runnable() { + @Override + public void run() { + testContainerView.setMaxScrollX(maxScrollXPix); + testContainerView.setMaxScrollY(maxScrollYPix); + } + }); + } + + private boolean checkScrollOnMainSync(final ScrollTestContainerView testContainerView, + final int scrollXPix, final int scrollYPix) { + final AtomicBoolean equal = new AtomicBoolean(false); + getInstrumentation().runOnMainSync(new Runnable() { + @Override + public void run() { + equal.set((scrollXPix == testContainerView.getScrollX()) && + (scrollYPix == testContainerView.getScrollY())); + } + }); + return equal.get(); + } + + private void assertScrollOnMainSync(final ScrollTestContainerView testContainerView, + final int scrollXPix, final int scrollYPix) { + getInstrumentation().runOnMainSync(new Runnable() { + @Override + public void run() { + assertEquals(scrollXPix, testContainerView.getScrollX()); + assertEquals(scrollYPix, testContainerView.getScrollY()); + } + }); + } + + private void assertScrollInJs(AwContents awContents, TestAwContentsClient contentsClient, + int xCss, 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); + } + + private void loadTestPageAndWaitForFirstFrame(final ScrollTestContainerView testContainerView, + final TestAwContentsClient contentsClient, + final String onscrollObserverName) throws Exception { + final JavascriptEventObserver firstFrameObserver = new JavascriptEventObserver(); + final String firstFrameObserverName = "firstFrameObserver"; + enableJavaScriptOnUiThread(testContainerView.getAwContents()); + + getInstrumentation().runOnMainSync(new Runnable() { + @Override + public void run() { + firstFrameObserver.register(testContainerView.getContentViewCore(), + firstFrameObserverName); + } + }); + + // 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); + + // We wait for "a couple" of frames for the active tree in CC to stabilize and for pending + // tree activations to stop clobbering the root scroll layer's scroll offset. This wait + // 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 + @Feature({"AndroidWebView"}) + public void testUiScrollReflectedInJs() throws Throwable { + final TestAwContentsClient contentsClient = new TestAwContentsClient(); + final ScrollTestContainerView testContainerView = + (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient); + enableJavaScriptOnUiThread(testContainerView.getAwContents()); + + final double deviceDIPScale = + DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale(); + final int targetScrollXCss = 233; + final int targetScrollYCss = 322; + final int targetScrollXPix = (int) Math.round(targetScrollXCss * deviceDIPScale); + final int targetScrollYPix = (int) Math.round(targetScrollYCss * deviceDIPScale); + final JavascriptEventObserver onscrollObserver = new JavascriptEventObserver(); + + Log.w("AndroidScrollIntegrationTest", String.format("scroll in Js (%d, %d) -> (%d, %d)", + targetScrollXCss, targetScrollYCss, targetScrollXPix, targetScrollYPix)); + + getInstrumentation().runOnMainSync(new Runnable() { + @Override + public void run() { + onscrollObserver.register(testContainerView.getContentViewCore(), + "onscrollObserver"); + } + }); + + loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, "onscrollObserver"); + + scrollToOnMainSync(testContainerView, targetScrollXPix, targetScrollYPix); + + onscrollObserver.waitForEvent(SCROLL_OFFSET_PROPAGATION_TIMEOUT_MS); + assertScrollInJs(testContainerView.getAwContents(), contentsClient, + targetScrollXCss, targetScrollYCss); + } + + @SmallTest + @Feature({"AndroidWebView"}) + public void testJsScrollReflectedInUi() throws Throwable { + final TestAwContentsClient contentsClient = new TestAwContentsClient(); + final ScrollTestContainerView testContainerView = + (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient); + enableJavaScriptOnUiThread(testContainerView.getAwContents()); + + final double deviceDIPScale = + DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale(); + final int targetScrollXCss = 132; + final int targetScrollYCss = 243; + final int targetScrollXPix = (int) Math.round(targetScrollXCss * deviceDIPScale); + final int targetScrollYPix = (int) Math.round(targetScrollYCss * deviceDIPScale); + + loadDataSync(testContainerView.getAwContents(), contentsClient.getOnPageFinishedHelper(), + makeTestPage(null, null), "text/html", false); + + final CallbackHelper onScrollToCallbackHelper = + testContainerView.getOnScrollToCallbackHelper(); + final int scrollToCallCount = onScrollToCallbackHelper.getCallCount(); + executeJavaScriptAndWaitForResult(testContainerView.getAwContents(), contentsClient, + String.format("window.scrollTo(%d, %d);", targetScrollXCss, targetScrollYCss)); + onScrollToCallbackHelper.waitForCallback(scrollToCallCount); + + assertScrollOnMainSync(testContainerView, targetScrollXPix, targetScrollYPix); + } + + @SmallTest + @Feature({"AndroidWebView"}) + public void testJsScrollCanBeAlteredByUi() throws Throwable { + final TestAwContentsClient contentsClient = new TestAwContentsClient(); + final ScrollTestContainerView testContainerView = + (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient); + enableJavaScriptOnUiThread(testContainerView.getAwContents()); + + final double deviceDIPScale = + DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale(); + final int targetScrollXCss = 132; + final int targetScrollYCss = 243; + final int targetScrollXPix = (int) Math.round(targetScrollXCss * deviceDIPScale); + final int targetScrollYPix = (int) Math.round(targetScrollYCss * deviceDIPScale); + + final int maxScrollXCss = 101; + final int maxScrollYCss = 201; + final int maxScrollXPix = (int) Math.round(maxScrollXCss * deviceDIPScale); + final int maxScrollYPix = (int) Math.round(maxScrollYCss * deviceDIPScale); + + loadDataSync(testContainerView.getAwContents(), contentsClient.getOnPageFinishedHelper(), + makeTestPage(null, null), "text/html", false); + + setMaxScrollOnMainSync(testContainerView, maxScrollXPix, maxScrollYPix); + + final CallbackHelper onScrollToCallbackHelper = + testContainerView.getOnScrollToCallbackHelper(); + final int scrollToCallCount = onScrollToCallbackHelper.getCallCount(); + executeJavaScriptAndWaitForResult(testContainerView.getAwContents(), contentsClient, + "window.scrollTo(" + targetScrollXCss + "," + targetScrollYCss + ")"); + onScrollToCallbackHelper.waitForCallback(scrollToCallCount); + + assertScrollOnMainSync(testContainerView, maxScrollXPix, maxScrollYPix); + } + + private void dragViewBy(AwTestContainerView testContainerView, int dxPix, int dyPix, + int steps) { + int gestureStart[] = new int[2]; + testContainerView.getLocationOnScreen(gestureStart); + int gestureEnd[] = new int[] { gestureStart[0] - dxPix, gestureStart[1] - dyPix }; + + TestTouchUtils.drag(this, gestureStart[0], gestureEnd[0], gestureStart[1], gestureEnd[1], + steps); + } + + @SmallTest + @Feature({"AndroidWebView"}) + public void testTouchScrollCanBeAlteredByUi() throws Throwable { + final TestAwContentsClient contentsClient = new TestAwContentsClient(); + final ScrollTestContainerView testContainerView = + (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient); + enableJavaScriptOnUiThread(testContainerView.getAwContents()); + + final int dragSteps = 10; + final int dragStepSize = 24; + // Watch out when modifying - if the y or x delta aren't big enough vertical or horizontal + // scroll snapping will kick in. + final int targetScrollXPix = dragStepSize * dragSteps; + final int targetScrollYPix = dragStepSize * dragSteps; + + final double deviceDIPScale = + DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale(); + final int maxScrollXPix = 101; + final int maxScrollYPix = 211; + // Make sure we can't hit these values simply as a result of scrolling. + assert (maxScrollXPix % dragStepSize) != 0; + assert (maxScrollYPix % dragStepSize) != 0; + final int maxScrollXCss = (int) Math.round(maxScrollXPix / deviceDIPScale); + final int maxScrollYCss = (int) Math.round(maxScrollYPix / deviceDIPScale); + + setMaxScrollOnMainSync(testContainerView, maxScrollXPix, maxScrollYPix); + + loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null); + + final CallbackHelper onScrollToCallbackHelper = + testContainerView.getOnScrollToCallbackHelper(); + final int scrollToCallCount = onScrollToCallbackHelper.getCallCount(); + dragViewBy(testContainerView, targetScrollXPix, targetScrollYPix, dragSteps); + + for (int i = 1; i <= dragSteps; ++i) { + onScrollToCallbackHelper.waitForCallback(scrollToCallCount, i); + if (checkScrollOnMainSync(testContainerView, maxScrollXPix, maxScrollYPix)) + break; + } + + assertScrollOnMainSync(testContainerView, maxScrollXPix, maxScrollYPix); + assertScrollInJs(testContainerView.getAwContents(), contentsClient, + maxScrollXCss, maxScrollYCss); + } +} diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AndroidViewIntegrationTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AndroidViewIntegrationTest.java index 72e62a6..9ba9419 100644 --- a/android_webview/javatests/src/org/chromium/android_webview/test/AndroidViewIntegrationTest.java +++ b/android_webview/javatests/src/org/chromium/android_webview/test/AndroidViewIntegrationTest.java @@ -65,11 +65,14 @@ public class AndroidViewIntegrationTest extends AwTestBase { } } - private class DependencyFactory extends TestDependencyFactory { - @Override - public AwLayoutSizer createLayoutSizer() { - return new TestAwLayoutSizer(); - } + @Override + protected TestDependencyFactory createTestDependencyFactory() { + return new TestDependencyFactory() { + @Override + public AwLayoutSizer createLayoutSizer() { + return new TestAwLayoutSizer(); + } + }; } final LinearLayout.LayoutParams wrapContentLayoutParams = @@ -82,8 +85,7 @@ public class AndroidViewIntegrationTest extends AwTestBase { getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { - testContainerView.set(createAwTestContainerView(awContentsClient, - new DependencyFactory())); + testContainerView.set(createAwTestContainerView(awContentsClient)); testContainerView.get().setLayoutParams(wrapContentLayoutParams); testContainerView.get().setVisibility(visibility); } @@ -98,8 +100,7 @@ public class AndroidViewIntegrationTest extends AwTestBase { getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { - testContainerView.set(createDetachedAwTestContainerView(awContentsClient, - new DependencyFactory())); + testContainerView.set(createDetachedAwTestContainerView(awContentsClient)); } }); return testContainerView.get(); 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 52957e0..da078d2 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 @@ -200,18 +200,18 @@ public class AwTestBase public AwLayoutSizer createLayoutSizer() { return new AwLayoutSizer(); } + public AwTestContainerView createAwTestContainerView(AwTestRunnerActivity activity) { + return new AwTestContainerView(activity); + } } - protected AwTestContainerView createAwTestContainerView( - final AwContentsClient awContentsClient) { - return createAwTestContainerView(awContentsClient, new TestDependencyFactory()); + protected TestDependencyFactory createTestDependencyFactory() { + return new TestDependencyFactory(); } protected AwTestContainerView createAwTestContainerView( - final AwContentsClient awContentsClient, - final TestDependencyFactory testDependencyFactory) { - AwTestContainerView testContainerView = - createDetachedAwTestContainerView(awContentsClient, testDependencyFactory); + final AwContentsClient awContentsClient) { + AwTestContainerView testContainerView = createDetachedAwTestContainerView(awContentsClient); getActivity().addView(testContainerView); testContainerView.requestFocus(); return testContainerView; @@ -222,9 +222,10 @@ public class AwTestBase new AwBrowserContext(new InMemorySharedPreferences()); protected AwTestContainerView createDetachedAwTestContainerView( - final AwContentsClient awContentsClient, - final TestDependencyFactory testDependencyFactory) { - final AwTestContainerView testContainerView = new AwTestContainerView(getActivity()); + final AwContentsClient awContentsClient) { + final TestDependencyFactory testDependencyFactory = createTestDependencyFactory(); + final AwTestContainerView testContainerView = + testDependencyFactory.createAwTestContainerView(getActivity()); testContainerView.initialize(new AwContents( mBrowserContext, testContainerView, testContainerView.getInternalAccessDelegate(), awContentsClient, false, testDependencyFactory.createLayoutSizer())); @@ -233,12 +234,6 @@ public class AwTestBase protected AwTestContainerView createAwTestContainerViewOnMainSync( final AwContentsClient client) throws Exception { - return createAwTestContainerViewOnMainSync(client, new TestDependencyFactory()); - } - - protected AwTestContainerView createAwTestContainerViewOnMainSync( - final AwContentsClient client, - final TestDependencyFactory testDependencyFactory) throws Exception { final AtomicReference<AwTestContainerView> testContainerView = new AtomicReference<AwTestContainerView>(); getInstrumentation().runOnMainSync(new Runnable() { diff --git a/android_webview/native/aw_contents.cc b/android_webview/native/aw_contents.cc index 2384c24..89c1cf9 100644 --- a/android_webview/native/aw_contents.cc +++ b/android_webview/native/aw_contents.cc @@ -646,7 +646,7 @@ bool AwContents::OnDraw(JNIEnv* env, jint clip_bottom) { return browser_view_renderer_->OnDraw(canvas, is_hardware_accelerated, - gfx::Point(scroll_x, scroll_y), + gfx::Vector2d(scroll_x, scroll_y), gfx::Rect(clip_left, clip_top, clip_right - clip_left, @@ -686,6 +686,24 @@ gfx::Point AwContents::GetLocationOnScreen() { return gfx::Point(location[0], location[1]); } +void AwContents::ScrollContainerViewTo(gfx::Vector2d new_value) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jobject> obj = java_ref_.get(env); + if (obj.is_null()) + return; + Java_AwContents_scrollContainerViewTo( + env, obj.obj(), new_value.x(), new_value.y()); +} + + +void AwContents::SetDipScale(JNIEnv* env, jobject obj, jfloat dipScale) { + browser_view_renderer_->SetDipScale(dipScale); +} + +void AwContents::ScrollTo(JNIEnv* env, jobject obj, jint xPix, jint yPix) { + browser_view_renderer_->ScrollTo(gfx::Vector2d(xPix, yPix)); +} + void AwContents::OnPageScaleFactorChanged(float page_scale_factor) { JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef<jobject> obj = java_ref_.get(env); diff --git a/android_webview/native/aw_contents.h b/android_webview/native/aw_contents.h index e2e72c4..15761b98 100644 --- a/android_webview/native/aw_contents.h +++ b/android_webview/native/aw_contents.h @@ -149,11 +149,15 @@ class AwContents : public FindHelper::Listener, virtual void PostInvalidate() OVERRIDE; virtual void OnNewPicture() OVERRIDE; virtual gfx::Point GetLocationOnScreen() OVERRIDE; + virtual void ScrollContainerViewTo(gfx::Vector2d new_value) OVERRIDE; void ClearCache(JNIEnv* env, jobject obj, jboolean include_disk_files); void SetPendingWebContentsForPopup(scoped_ptr<content::WebContents> pending); jint ReleasePopupAwContents(JNIEnv* env, jobject obj); + void ScrollTo(JNIEnv* env, jobject obj, jint xPix, jint yPix); + void SetDipScale(JNIEnv* env, jobject obj, jfloat dipScale); + void SetSaveFormData(bool enabled); private: 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 89905d1..f3a3e88 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 @@ -56,16 +56,19 @@ public class AwTestContainerView extends FrameLayout { @Override public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); mAwContents.onConfigurationChanged(newConfig); } @Override public void onAttachedToWindow() { + super.onAttachedToWindow(); mAwContents.onAttachedToWindow(); } @Override public void onDetachedFromWindow() { + super.onDetachedFromWindow(); mAwContents.onDetachedFromWindow(); } @@ -97,27 +100,40 @@ public class AwTestContainerView extends FrameLayout { @Override public void onSizeChanged(int w, int h, int ow, int oh) { + super.onSizeChanged(w, h, ow, oh); mAwContents.onSizeChanged(w, h, ow, oh); } @Override + public void onScrollChanged(int l, int t, int oldl, int oldt) { + super.onScrollChanged(l, t, oldl, oldt); + if (mAwContents != null) { + mAwContents.onContainerViewScrollChanged(l, t, oldl, oldt); + } + } + + @Override public void onVisibilityChanged(View changedView, int visibility) { + super.onVisibilityChanged(changedView, visibility); mAwContents.onVisibilityChanged(changedView, visibility); } @Override public void onWindowVisibilityChanged(int visibility) { + super.onWindowVisibilityChanged(visibility); mAwContents.onWindowVisibilityChanged(visibility); } @Override public boolean onTouchEvent(MotionEvent ev) { + super.onTouchEvent(ev); return mAwContents.onTouchEvent(ev); } @Override public void onDraw(Canvas canvas) { mAwContents.onDraw(canvas); + super.onDraw(canvas); } // TODO: AwContents could define a generic class that holds an implementation similar to diff --git a/content/public/android/java/src/org/chromium/content/browser/ContentView.java b/content/public/android/java/src/org/chromium/content/browser/ContentView.java index a562efa..22f8fe8 100644 --- a/content/public/android/java/src/org/chromium/content/browser/ContentView.java +++ b/content/public/android/java/src/org/chromium/content/browser/ContentView.java @@ -417,12 +417,6 @@ public class ContentView extends FrameLayout return super.drawChild(canvas, child, drawingTime); } - // Needed by ContentViewCore.InternalAccessDelegate - @Override - public void onScrollChanged(int l, int t, int oldl, int oldt) { - super.onScrollChanged(l, t, oldl, oldt); - } - @Override protected void onSizeChanged(int w, int h, int ow, int oh) { TraceEvent.begin(); diff --git a/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java b/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java index 58ff66c..eeebf03 100644 --- a/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java +++ b/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java @@ -153,11 +153,6 @@ public class ContentViewCore implements MotionEventDelegate, NavigationClient { void super_onConfigurationChanged(Configuration newConfig); /** - * @see View#onScrollChanged(int, int, int, int) - */ - void onScrollChanged(int lPix, int tPix, int oldlPix, int oldtPix); - - /** * @see View#awakenScrollBars() */ boolean awakenScrollBars(); @@ -2149,14 +2144,6 @@ public class ContentViewCore implements MotionEventDelegate, NavigationClient { if (needHidePopupZoomer) mPopupZoomer.hide(true); - if (scrollChanged) { - mContainerViewInternals.onScrollChanged( - (int) mRenderCoordinates.fromLocalCssToPix(scrollOffsetX), - (int) mRenderCoordinates.fromLocalCssToPix(scrollOffsetY), - (int) mRenderCoordinates.getScrollXPix(), - (int) mRenderCoordinates.getScrollYPix()); - } - if (pageScaleChanged) { // This function should be called back from native as soon // as the scroll is applied to the backbuffer. We should only diff --git a/content/renderer/gpu/input_handler_manager_client.h b/content/renderer/gpu/input_handler_manager_client.h index a6a6db8..a98a359 100644 --- a/content/renderer/gpu/input_handler_manager_client.h +++ b/content/renderer/gpu/input_handler_manager_client.h @@ -8,6 +8,7 @@ #include "base/basictypes.h" #include "base/callback_forward.h" #include "content/common/content_export.h" +#include "ui/gfx/vector2d_f.h" namespace ui { struct LatencyInfo; @@ -17,10 +18,6 @@ namespace cc { class InputHandler; } -namespace gfx { -class Vector2dF; -} - namespace WebKit { class WebInputEvent; } diff --git a/ui/gfx/vector2d_f.h b/ui/gfx/vector2d_f.h index fdd3a9e..4faf28a 100644 --- a/ui/gfx/vector2d_f.h +++ b/ui/gfx/vector2d_f.h @@ -70,6 +70,10 @@ inline bool operator==(const Vector2dF& lhs, const Vector2dF& rhs) { return lhs.x() == rhs.x() && lhs.y() == rhs.y(); } +inline bool operator!=(const Vector2dF& lhs, const Vector2dF& rhs) { + return !(lhs == rhs); +} + inline Vector2dF operator-(const Vector2dF& v) { return Vector2dF(-v.x(), -v.y()); } |