diff options
author | mkosiba@chromium.org <mkosiba@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-04-19 12:46:38 +0000 |
---|---|---|
committer | mkosiba@chromium.org <mkosiba@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-04-19 12:46:38 +0000 |
commit | 5614eae1b3ee87cdc9513f3e75b57d9c29d71a3f (patch) | |
tree | 7eb63ebb169d5de3b6b541bad928f0417940dca1 /android_webview | |
parent | 656d0996620653ee8aa5c8ce4ee3cd48ff35a5f0 (diff) | |
download | chromium_src-5614eae1b3ee87cdc9513f3e75b57d9c29d71a3f.zip chromium_src-5614eae1b3ee87cdc9513f3e75b57d9c29d71a3f.tar.gz chromium_src-5614eae1b3ee87cdc9513f3e75b57d9c29d71a3f.tar.bz2 |
[android] Resize the android_webview if it's 0x0 initially.
This changes the content size update path for android_webview to use
the preferred size RenderView mechanism instead of the
CompositorFrameMetadata.
The reason for the change is due to the fact that the CompositorFrameMetadata
is not updated when the view size is 0x0, which is a common use case
for the WebView when it's layout mode is set to "wrap content".
BUG=b/8187850
TEST=AndroidWebViewTests
Review URL: https://chromiumcodereview.appspot.com/12567020
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@195135 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'android_webview')
22 files changed, 820 insertions, 198 deletions
diff --git a/android_webview/browser/browser_view_renderer.h b/android_webview/browser/browser_view_renderer.h index 6c246bf..5c485f0 100644 --- a/android_webview/browser/browser_view_renderer.h +++ b/android_webview/browser/browser_view_renderer.h @@ -39,6 +39,9 @@ class BrowserViewRenderer { // Called to get view's absolute location on the screen. virtual gfx::Point GetLocationOnScreen() = 0; + // Called when the RenderView page scale changes. + virtual void OnPageScaleFactorChanged(float page_scale_factor) = 0; + protected: virtual ~Client() {} }; diff --git a/android_webview/browser/browser_view_renderer_impl.cc b/android_webview/browser/browser_view_renderer_impl.cc index 83fc87d..6ace7ec 100644 --- a/android_webview/browser/browser_view_renderer_impl.cc +++ b/android_webview/browser/browser_view_renderer_impl.cc @@ -476,6 +476,17 @@ void BrowserViewRendererImpl::OnPictureUpdated(int process_id, Invalidate(); } +void BrowserViewRendererImpl::OnPageScaleFactorChanged( + int process_id, + int render_view_id, + float page_scale_factor) { + CHECK_EQ(web_contents_->GetRenderProcessHost()->GetID(), process_id); + if (render_view_id != web_contents_->GetRoutingID()) + return; + + client_->OnPageScaleFactorChanged(page_scale_factor); +} + void BrowserViewRendererImpl::SetCompositorVisibility(bool visible) { if (compositor_visible_ != visible) { compositor_visible_ = visible; diff --git a/android_webview/browser/browser_view_renderer_impl.h b/android_webview/browser/browser_view_renderer_impl.h index 3a5170e..1de1794 100644 --- a/android_webview/browser/browser_view_renderer_impl.h +++ b/android_webview/browser/browser_view_renderer_impl.h @@ -66,6 +66,9 @@ class BrowserViewRendererImpl // ViewRendererHost::Client implementation. virtual void OnPictureUpdated(int process_id, int render_view_id) OVERRIDE; + virtual void OnPageScaleFactorChanged(int process_id, + int render_view_id, + float page_scale_factor) OVERRIDE; protected: BrowserViewRendererImpl(BrowserViewRenderer::Client* client, diff --git a/android_webview/browser/renderer_host/view_renderer_host.cc b/android_webview/browser/renderer_host/view_renderer_host.cc index e6a354c..1a21b04 100644 --- a/android_webview/browser/renderer_host/view_renderer_host.cc +++ b/android_webview/browser/renderer_host/view_renderer_host.cc @@ -21,6 +21,7 @@ ViewRendererHost::ViewRendererHost(content::WebContents* contents, Client* client) : content::WebContentsObserver(contents), client_(client) { + DCHECK(client); } ViewRendererHost::~ViewRendererHost() { @@ -40,10 +41,8 @@ void ViewRendererHost::EnableCapturePictureCallback(bool enabled) { } void ViewRendererHost::OnPictureUpdated() { - if (client_) { - client_->OnPictureUpdated(web_contents()->GetRenderProcessHost()->GetID(), - routing_id()); - } + client_->OnPictureUpdated(web_contents()->GetRenderProcessHost()->GetID(), + routing_id()); } void ViewRendererHost::OnDidActivateAcceleratedCompositing( @@ -68,6 +67,13 @@ void ViewRendererHost::OnDidActivateAcceleratedCompositing( content_view_core->SetInputHandler(input_handler); } +void ViewRendererHost::OnPageScaleFactorChanged(float page_scale_factor) { + client_->OnPageScaleFactorChanged( + web_contents()->GetRenderProcessHost()->GetID(), + routing_id(), + page_scale_factor); +} + void ViewRendererHost::RenderViewGone(base::TerminationStatus status) { DCHECK(CalledOnValidThread()); RendererPictureMap::GetInstance()->ClearRendererPicture( @@ -81,6 +87,8 @@ bool ViewRendererHost::OnMessageReceived(const IPC::Message& message) { OnPictureUpdated) IPC_MESSAGE_HANDLER(AwViewHostMsg_DidActivateAcceleratedCompositing, OnDidActivateAcceleratedCompositing) + IPC_MESSAGE_HANDLER(AwViewHostMsg_PageScaleFactorChanged, + OnPageScaleFactorChanged) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() diff --git a/android_webview/browser/renderer_host/view_renderer_host.h b/android_webview/browser/renderer_host/view_renderer_host.h index 4cb2501..42b9607 100644 --- a/android_webview/browser/renderer_host/view_renderer_host.h +++ b/android_webview/browser/renderer_host/view_renderer_host.h @@ -18,6 +18,9 @@ class ViewRendererHost : public content::WebContentsObserver, class Client { public: virtual void OnPictureUpdated(int process_id, int render_view_id) = 0; + virtual void OnPageScaleFactorChanged(int process_id, + int render_view_id, + float page_scale_factor) = 0; protected: virtual ~Client() {} @@ -43,6 +46,7 @@ class ViewRendererHost : public content::WebContentsObserver, void OnPictureUpdated(); void OnDidActivateAcceleratedCompositing(int input_handler_id); + void OnPageScaleFactorChanged(float page_scale_factor); bool IsRenderViewReady() const; diff --git a/android_webview/common/render_view_messages.h b/android_webview/common/render_view_messages.h index f3c60db..6d4ce7d 100644 --- a/android_webview/common/render_view_messages.h +++ b/android_webview/common/render_view_messages.h @@ -87,6 +87,10 @@ IPC_MESSAGE_ROUTED2(AwViewHostMsg_DocumentHasImagesResponse, IPC_MESSAGE_ROUTED1(AwViewHostMsg_UpdateHitTestData, android_webview::AwHitTestData) +// Sent whenever the page scale factor (as seen by RenderView) is changed. +IPC_MESSAGE_ROUTED1(AwViewHostMsg_PageScaleFactorChanged, + float /* page_scale_factor */) + // Notification that a new picture becomes available. It is only sent if // AwViewMsg_EnableCapturePictureCallback was previously enabled. IPC_MESSAGE_ROUTED0(AwViewHostMsg_PictureUpdated) 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 1d8651c..ecfb146 100644 --- a/android_webview/java/src/org/chromium/android_webview/AwContents.java +++ b/android_webview/java/src/org/chromium/android_webview/AwContents.java @@ -28,6 +28,8 @@ import android.view.inputmethod.InputConnection; import android.webkit.GeolocationPermissions; import android.webkit.ValueCallback; +import com.google.common.annotations.VisibleForTesting; + import org.chromium.base.CalledByNative; import org.chromium.base.JNINamespace; import org.chromium.base.ThreadUtils; @@ -91,23 +93,13 @@ public class AwContents { void setMeasuredDimension(int measuredWidth, int measuredHeight); } - /** - * Listener for renderer state change notifications coming through ContentViewCore. - */ - private class AwContentStateChangeListener - implements ContentViewCore.ContentSizeChangeListener { - @Override - public void onContentSizeChanged(int contentWidthPix, int contentHeightPix) { - mLayoutSizer.onContentSizeChanged(contentWidthPix, contentHeightPix); - } - } - private int mNativeAwContents; private AwBrowserContext mBrowserContext; private ViewGroup mContainerView; private ContentViewCore mContentViewCore; private AwContentsClient mContentsClient; private AwContentsClientBridge mContentsClientBridge; + private AwWebContentsDelegate mWebContentsDelegate; private AwContentsIoThreadClient mIoThreadClient; private InterceptNavigationDelegateImpl mInterceptNavigationDelegate; private InternalAccessDelegate mInternalAccessAdapter; @@ -138,6 +130,7 @@ public class AwContents { private CleanupReference mCleanupReference; + //-------------------------------------------------------------------------------------------- private class IoThreadClientImpl implements AwContentsIoThreadClient { // All methods are called on the IO thread. @@ -203,6 +196,7 @@ public class AwContents { } } + //-------------------------------------------------------------------------------------------- private class InterceptNavigationDelegateImpl implements InterceptNavigationDelegate { private String mLastLoadUrlAddress; @@ -247,6 +241,7 @@ public class AwContents { } } + //-------------------------------------------------------------------------------------------- private class AwLayoutSizerDelegate implements AwLayoutSizer.Delegate { @Override public void requestLayout() { @@ -259,26 +254,65 @@ public class AwContents { } } + //-------------------------------------------------------------------------------------------- + private class AwPinchGestureStateListener implements ContentViewCore.PinchGestureStateListener { + @Override + public void onPinchGestureStart() { + // While it's possible to re-layout the view during a pinch gesture, the effect is very + // janky (especially that the page scale update notification comes from the renderer + // main thread, not from the impl thread, so it's usually out of sync with what's on + // screen). It's also quite expensive to do a re-layout, so we simply postpone + // re-layout for the duration of the gesture. This is compatible with what + // WebViewClassic does. + mLayoutSizer.freezeLayoutRequests(); + } + + public void onPinchGestureEnd() { + mLayoutSizer.unfreezeLayoutRequests(); + } + } + /** * @param browserContext the browsing context to associate this view contents with. * @param containerView the view-hierarchy item this object will be bound to. * @param internalAccessAdapter to access private methods on containerView. * @param contentsClient will receive API callbacks from this WebView Contents * @param isAccessFromFileURLsGrantedByDefault passed to ContentViewCore.initialize. + * + * This constructor uses the default view sizing policy. */ public AwContents(AwBrowserContext browserContext, ViewGroup containerView, InternalAccessDelegate internalAccessAdapter, AwContentsClient contentsClient, boolean isAccessFromFileURLsGrantedByDefault) { + this(browserContext, containerView, internalAccessAdapter, contentsClient, + isAccessFromFileURLsGrantedByDefault, new AwLayoutSizer()); + } + + /** + * @param layoutSizer the AwLayoutSizer instance implementing the sizing policy for the view. + * + * This version of the constructor is used in test code to inject test versions of the above + * documented classes + */ + public AwContents(AwBrowserContext browserContext, ViewGroup containerView, + InternalAccessDelegate internalAccessAdapter, AwContentsClient contentsClient, + boolean isAccessFromFileURLsGrantedByDefault, AwLayoutSizer layoutSizer) { mBrowserContext = browserContext; mContainerView = containerView; mInternalAccessAdapter = internalAccessAdapter; + mDIPScale = DeviceDisplayInfo.create(containerView.getContext()).getDIPScale(); // Note that ContentViewCore must be set up before AwContents, as ContentViewCore // setup performs process initialisation work needed by AwContents. mContentViewCore = new ContentViewCore(containerView.getContext(), ContentViewCore.PERSONALITY_VIEW); + mContentViewCore.setPinchGestureStateListener(new AwPinchGestureStateListener()); mContentsClientBridge = new AwContentsClientBridge(contentsClient); - mNativeAwContents = nativeInit(contentsClient.getWebContentsDelegate(), - mContentsClientBridge); + mLayoutSizer = layoutSizer; + mLayoutSizer.setDelegate(new AwLayoutSizerDelegate()); + mLayoutSizer.setDIPScale(mDIPScale); + mWebContentsDelegate = new AwWebContentsDelegateAdapter(contentsClient, + mLayoutSizer.getPreferredSizeChangedListener()); + mNativeAwContents = nativeInit(mWebContentsDelegate, mContentsClientBridge); mContentsClient = contentsClient; mCleanupReference = new CleanupReference(this, new DestroyRunnable(mNativeAwContents)); @@ -286,8 +320,6 @@ public class AwContents { mContentViewCore.initialize(containerView, internalAccessAdapter, nativeWebContents, null, isAccessFromFileURLsGrantedByDefault); mContentViewCore.setContentViewClient(mContentsClient); - mLayoutSizer = new AwLayoutSizer(new AwLayoutSizerDelegate()); - mContentViewCore.setContentSizeChangeListener(new AwContentStateChangeListener()); mContentsClient.installWebContentsObserver(mContentViewCore); mSettings = new AwSettings(mContentViewCore.getContext(), nativeWebContents); @@ -298,7 +330,6 @@ public class AwContents { nativeDidInitializeContentViewCore(mNativeAwContents, mContentViewCore.getNativeContentViewCore()); - mDIPScale = DeviceDisplayInfo.create(containerView.getContext()).getDIPScale(); mContentsClient.setDIPScale(mDIPScale); mSettings.setDIPScale(mDIPScale); mDefaultVideoPosterRequestHandler = new DefaultVideoPosterRequestHandler(mContentsClient); @@ -309,7 +340,7 @@ public class AwContents { new AwContentVideoViewDelegate(contentsClient, containerView.getContext())); } - // TODO(mkosiba): Remove this once we move the embedding layer to use methods on AwContents. + @VisibleForTesting public ContentViewCore getContentViewCore() { return mContentViewCore; } @@ -336,7 +367,7 @@ public class AwContents { // We explicitly do not null out the mContentViewCore reference here // because ContentViewCore already has code to deal with the case // methods are called on it after it's been destroyed, and other - // code relies on AwContents.getContentViewCore to return non-null. + // code relies on AwContents.mContentViewCore to be non-null. mCleanupReference.cleanupNow(); mNativeAwContents = 0; } @@ -432,7 +463,7 @@ public class AwContents { */ public int getMostRecentProgress() { // WebContentsDelegateAndroid conveniently caches the most recent notified value for us. - return mContentsClient.getWebContentsDelegate().getMostRecentProgress(); + return mWebContentsDelegate.getMostRecentProgress(); } public Bitmap getFavicon() { @@ -1245,6 +1276,12 @@ public class AwContents { return result; } + @CalledByNative + private void onPageScaleFactorChanged(float pageScaleFactor) { + // This change notification comes from the renderer thread, not from the cc/ impl thread. + mLayoutSizer.onPageScaleChanged(pageScaleFactor); + } + // ------------------------------------------------------------------------------------------- // Helper methods // ------------------------------------------------------------------------------------------- diff --git a/android_webview/java/src/org/chromium/android_webview/AwContentsClient.java b/android_webview/java/src/org/chromium/android_webview/AwContentsClient.java index 3862b6d..8f9f0f9 100644 --- a/android_webview/java/src/org/chromium/android_webview/AwContentsClient.java +++ b/android_webview/java/src/org/chromium/android_webview/AwContentsClient.java @@ -39,10 +39,6 @@ import org.chromium.net.NetError; public abstract class AwContentsClient extends ContentViewClient { private static final String TAG = "AwContentsClient"; - // Handler for WebContentsDelegate callbacks - private final WebContentsDelegateAdapter mWebContentsDelegateAdapter = - new WebContentsDelegateAdapter(); - private final AwContentsClientCallbackHelper mCallbackHelper = new AwContentsClientCallbackHelper(this); @@ -50,116 +46,6 @@ public abstract class AwContentsClient extends ContentViewClient { private double mDIPScale; - //-------------------------------------------------------------------------------------------- - // Adapter for WebContentsDelegate methods. - //-------------------------------------------------------------------------------------------- - class WebContentsDelegateAdapter extends AwWebContentsDelegate { - - @Override - public void onLoadProgressChanged(int progress) { - AwContentsClient.this.onProgressChanged(progress); - } - - @Override - public void handleKeyboardEvent(KeyEvent event) { - AwContentsClient.this.onUnhandledKeyEvent(event); - } - - @Override - public boolean addMessageToConsole(int level, String message, int lineNumber, - String sourceId) { - ConsoleMessage.MessageLevel messageLevel = ConsoleMessage.MessageLevel.DEBUG; - switch(level) { - case LOG_LEVEL_TIP: - messageLevel = ConsoleMessage.MessageLevel.TIP; - break; - case LOG_LEVEL_LOG: - messageLevel = ConsoleMessage.MessageLevel.LOG; - break; - case LOG_LEVEL_WARNING: - messageLevel = ConsoleMessage.MessageLevel.WARNING; - break; - case LOG_LEVEL_ERROR: - messageLevel = ConsoleMessage.MessageLevel.ERROR; - break; - default: - Log.w(TAG, "Unknown message level, defaulting to DEBUG"); - break; - } - - return AwContentsClient.this.onConsoleMessage( - new ConsoleMessage(message, sourceId, lineNumber, messageLevel)); - } - - @Override - public void onUpdateUrl(String url) { - // TODO: implement - } - - @Override - public void openNewTab(String url, boolean incognito) { - // TODO: implement - } - - @Override - public boolean addNewContents(int nativeSourceWebContents, int nativeWebContents, - int disposition, Rect initialPosition, boolean userGesture) { - // TODO: implement - return false; - } - - @Override - public void closeContents() { - AwContentsClient.this.onCloseWindow(); - } - - @Override - public void showRepostFormWarningDialog(final ContentViewCore contentViewCore) { - // This is intentionally not part of mCallbackHelper as that class is intended for - // callbacks going the other way (to the embedder, not from the embedder). - // TODO(mkosiba) We should be using something akin to the JsResultReceiver as the - // callback parameter (instead of ContentViewCore) and implement a way of converting - // that to a pair of messages. - final int MSG_CONTINUE_PENDING_RELOAD = 1; - final int MSG_CANCEL_PENDING_RELOAD = 2; - - // TODO(sgurun) Remember the URL to cancel the reload behavior - // if it is different than the most recent NavigationController entry. - final Handler handler = new Handler(Looper.getMainLooper()) { - @Override - public void handleMessage(Message msg) { - switch(msg.what) { - case MSG_CONTINUE_PENDING_RELOAD: { - contentViewCore.continuePendingReload(); - break; - } - case MSG_CANCEL_PENDING_RELOAD: { - contentViewCore.cancelPendingReload(); - break; - } - default: - throw new IllegalStateException( - "WebContentsDelegateAdapter: unhandled message " + msg.what); - } - } - }; - - Message resend = handler.obtainMessage(MSG_CONTINUE_PENDING_RELOAD); - Message dontResend = handler.obtainMessage(MSG_CANCEL_PENDING_RELOAD); - AwContentsClient.this.onFormResubmission(dontResend, resend); - } - - @Override - public boolean addNewContents(boolean isDialog, boolean isUserGesture) { - return AwContentsClient.this.onCreateWindow(isDialog, isUserGesture); - } - - @Override - public void activateContents() { - AwContentsClient.this.onRequestFocus(); - } - } - class AwWebContentsObserver extends WebContentsObserverAndroid { public AwWebContentsObserver(ContentViewCore contentViewCore) { super(contentViewCore); @@ -208,10 +94,6 @@ public abstract class AwContentsClient extends ContentViewClient { mDIPScale = dipScale; } - final AwWebContentsDelegate getWebContentsDelegate() { - return mWebContentsDelegateAdapter; - } - final AwContentsClientCallbackHelper getCallbackHelper() { return mCallbackHelper; } 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 d5ff108..7a48bfe 100644 --- a/android_webview/java/src/org/chromium/android_webview/AwLayoutSizer.java +++ b/android_webview/java/src/org/chromium/android_webview/AwLayoutSizer.java @@ -19,37 +19,116 @@ public class AwLayoutSizer { private boolean mWidthMeasurementIsFixed; private boolean mHeightMeasurementIsFixed; - // Size of the rendered content, as reported by native, in physical pixels. - private int mContentHeight; - private int mContentWidth; + // Size of the rendered content, as reported by native. + private int mContentHeightCss; + private int mContentWidthCss; + + // Page scale factor. This is set to zero initially so that we don't attempt to do a layout if + // we get the content size change notification first and a page scale change second. + private double mPageScaleFactor = 0.0; + + // Whether to postpone layout requests. + private boolean mFreezeLayoutRequests; + // Did we try to request a layout since the last time mPostponeLayoutRequests was set to true. + private boolean mFrozenLayoutRequestPending; + + private double mDIPScale; // Callback object for interacting with the View. - Delegate mDelegate; + private Delegate mDelegate; public interface Delegate { void requestLayout(); void setMeasuredDimension(int measuredWidth, int measuredHeight); } - public AwLayoutSizer(Delegate delegate) { + /** + * Default constructor. Note: both setDelegate and setDIPScale must be called before the class + * is ready for use. + */ + public AwLayoutSizer() { + } + + public void setDelegate(Delegate delegate) { mDelegate = delegate; } + public void setDIPScale(double dipScale) { + mDIPScale = dipScale; + } + + /** + * This is used to register the AwLayoutSizer to preferred content size change notifications in + * the AwWebContentsDelegate. + */ + public AwWebContentsDelegateAdapter.PreferredSizeChangedListener + getPreferredSizeChangedListener() { + return new AwWebContentsDelegateAdapter.PreferredSizeChangedListener() { + @Override + public void updatePreferredSize(int widthCss, int heightCss) { + onContentSizeChanged(widthCss, heightCss); + } + }; + } + + /** + * Postpone requesting layouts till unfreezeLayoutRequests is called. + */ + public void freezeLayoutRequests() { + mFreezeLayoutRequests = true; + mFrozenLayoutRequestPending = false; + } + + /** + * Stop postponing layout requests and request layout if such a request would have been made + * had the freezeLayoutRequests method not been called before. + */ + public void unfreezeLayoutRequests() { + mFreezeLayoutRequests = false; + if (mFrozenLayoutRequestPending) { + mFrozenLayoutRequestPending = false; + mDelegate.requestLayout(); + } + } + /** * Update the contents size. * This should be called whenever the content size changes (due to DOM manipulation or page * load, for example). - * The width and height should be in physical pixels. + * The width and height should be in CSS pixels. */ - public void onContentSizeChanged(int width, int height) { - boolean layoutNeeded = (mContentWidth != width && !mWidthMeasurementIsFixed) || - (mContentHeight != height && !mHeightMeasurementIsFixed); + public void onContentSizeChanged(int widthCss, int heightCss) { + doUpdate(widthCss, heightCss, mPageScaleFactor); + } - mContentWidth = width; - mContentHeight = height; + /** + * Update the contents page scale. + * This should be called whenever the content page scale factor changes (due to pinch zoom, for + * example). + */ + public void onPageScaleChanged(double pageScaleFactor) { + doUpdate(mContentWidthCss, mContentHeightCss, pageScaleFactor); + } + + private void doUpdate(int widthCss, int heightCss, double pageScaleFactor) { + // We want to request layout only if the size or scale change, however if any of the + // measurements are 'fixed', then changing the underlying size won't have any effect, so we + // ignore changes to dimensions that are 'fixed'. + boolean anyMeasurementNotFixed = !mWidthMeasurementIsFixed || !mHeightMeasurementIsFixed; + boolean layoutNeeded = (mContentWidthCss != widthCss && !mWidthMeasurementIsFixed) || + (mContentHeightCss != heightCss && !mHeightMeasurementIsFixed) || + (mPageScaleFactor != pageScaleFactor && anyMeasurementNotFixed); + + mContentWidthCss = widthCss; + mContentHeightCss = heightCss; + mPageScaleFactor = pageScaleFactor; if (layoutNeeded) { - mDelegate.requestLayout(); + if (mFreezeLayoutRequests) { + mFrozenLayoutRequestPending = true; + } else { + mDelegate.requestLayout(); + } } } @@ -66,6 +145,9 @@ public class AwLayoutSizer { int measuredHeight = heightSize; int measuredWidth = widthSize; + int contentHeightPix = (int) (mContentHeightCss * mPageScaleFactor * mDIPScale); + int contentWidthPix = (int) (mContentWidthCss * mPageScaleFactor * mDIPScale); + // Always use the given size unless unspecified. This matches WebViewClassic behavior. mWidthMeasurementIsFixed = (widthMode != MeasureSpec.UNSPECIFIED); // Freeze the height if an exact size is given by the parent or if the content size has @@ -73,21 +155,21 @@ public class AwLayoutSizer { // TODO(mkosiba): Actually we'd like the reduction in content size to cause the WebView to // shrink back again but only as a result of a page load. mHeightMeasurementIsFixed = (heightMode == MeasureSpec.EXACTLY) || - (heightMode == MeasureSpec.AT_MOST && mContentHeight > heightSize); + (heightMode == MeasureSpec.AT_MOST && contentHeightPix > heightSize); if (!mHeightMeasurementIsFixed) { - measuredHeight = mContentHeight; + measuredHeight = contentHeightPix; } if (!mWidthMeasurementIsFixed) { - measuredWidth = mContentWidth; + measuredWidth = contentWidthPix; } - if (measuredHeight < mContentHeight) { + if (measuredHeight < contentHeightPix) { measuredHeight |= View.MEASURED_STATE_TOO_SMALL; } - if (measuredWidth < mContentWidth) { + if (measuredWidth < contentWidthPix) { measuredWidth |= View.MEASURED_STATE_TOO_SMALL; } diff --git a/android_webview/java/src/org/chromium/android_webview/AwWebContentsDelegate.java b/android_webview/java/src/org/chromium/android_webview/AwWebContentsDelegate.java index 59c6422..23f48eb 100644 --- a/android_webview/java/src/org/chromium/android_webview/AwWebContentsDelegate.java +++ b/android_webview/java/src/org/chromium/android_webview/AwWebContentsDelegate.java @@ -24,4 +24,13 @@ public abstract class AwWebContentsDelegate extends WebContentsDelegateAndroid { @CalledByNative public abstract void activateContents(); + + /** + * Report a change in the preferred size. + * @param width preferred width in CSS pixels. + * @param height scroll height of the document element in CSS pixels. + */ + @CalledByNative + public void updatePreferredSize(int widthCss, int heightCss) { + } } diff --git a/android_webview/java/src/org/chromium/android_webview/AwWebContentsDelegateAdapter.java b/android_webview/java/src/org/chromium/android_webview/AwWebContentsDelegateAdapter.java new file mode 100644 index 0000000..0b888d3 --- /dev/null +++ b/android_webview/java/src/org/chromium/android_webview/AwWebContentsDelegateAdapter.java @@ -0,0 +1,152 @@ +// 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 android.graphics.Rect; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import android.view.KeyEvent; +import android.webkit.ConsoleMessage; + +import org.chromium.content.browser.ContentViewCore; + +/** + * Adapts the AwWebContentsDelegate interface to the AwContentsClient interface. + * This class also serves a secondary function of routing certain callbacks from the content layer + * to specific listener interfaces. + */ +class AwWebContentsDelegateAdapter extends AwWebContentsDelegate { + private static final String TAG = "AwWebContentsDelegateAdapter"; + + /** + * Listener definition for a callback to be invoked when the preferred size of the page + * contents changes. + */ + public interface PreferredSizeChangedListener { + /** + * Called when the preferred size of the page contents changes. + * @see AwWebContentsDelegate#updatePreferredSize + */ + void updatePreferredSize(int width, int height); + } + + final AwContentsClient mContentsClient; + final PreferredSizeChangedListener mPreferredSizeChangedListener; + + public AwWebContentsDelegateAdapter(AwContentsClient contentsClient, + PreferredSizeChangedListener preferredSizeChangedListener) { + mContentsClient = contentsClient; + mPreferredSizeChangedListener = preferredSizeChangedListener; + } + + @Override + public void onLoadProgressChanged(int progress) { + mContentsClient.onProgressChanged(progress); + } + + @Override + public void handleKeyboardEvent(KeyEvent event) { + mContentsClient.onUnhandledKeyEvent(event); + } + + @Override + public boolean addMessageToConsole(int level, String message, int lineNumber, + String sourceId) { + ConsoleMessage.MessageLevel messageLevel = ConsoleMessage.MessageLevel.DEBUG; + switch(level) { + case LOG_LEVEL_TIP: + messageLevel = ConsoleMessage.MessageLevel.TIP; + break; + case LOG_LEVEL_LOG: + messageLevel = ConsoleMessage.MessageLevel.LOG; + break; + case LOG_LEVEL_WARNING: + messageLevel = ConsoleMessage.MessageLevel.WARNING; + break; + case LOG_LEVEL_ERROR: + messageLevel = ConsoleMessage.MessageLevel.ERROR; + break; + default: + Log.w(TAG, "Unknown message level, defaulting to DEBUG"); + break; + } + + return mContentsClient.onConsoleMessage( + new ConsoleMessage(message, sourceId, lineNumber, messageLevel)); + } + + @Override + public void onUpdateUrl(String url) { + // TODO: implement + } + + @Override + public void openNewTab(String url, boolean incognito) { + // TODO: implement + } + + @Override + public boolean addNewContents(int nativeSourceWebContents, int nativeWebContents, + int disposition, Rect initialPosition, boolean userGesture) { + // TODO: implement + return false; + } + + @Override + public void closeContents() { + mContentsClient.onCloseWindow(); + } + + @Override + public void showRepostFormWarningDialog(final ContentViewCore contentViewCore) { + // TODO(mkosiba) We should be using something akin to the JsResultReceiver as the + // callback parameter (instead of ContentViewCore) and implement a way of converting + // that to a pair of messages. + final int MSG_CONTINUE_PENDING_RELOAD = 1; + final int MSG_CANCEL_PENDING_RELOAD = 2; + + // TODO(sgurun) Remember the URL to cancel the reload behavior + // if it is different than the most recent NavigationController entry. + final Handler handler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + switch(msg.what) { + case MSG_CONTINUE_PENDING_RELOAD: { + contentViewCore.continuePendingReload(); + break; + } + case MSG_CANCEL_PENDING_RELOAD: { + contentViewCore.cancelPendingReload(); + break; + } + default: + throw new IllegalStateException( + "WebContentsDelegateAdapter: unhandled message " + msg.what); + } + } + }; + + Message resend = handler.obtainMessage(MSG_CONTINUE_PENDING_RELOAD); + Message dontResend = handler.obtainMessage(MSG_CANCEL_PENDING_RELOAD); + mContentsClient.onFormResubmission(dontResend, resend); + } + + @Override + public boolean addNewContents(boolean isDialog, boolean isUserGesture) { + return mContentsClient.onCreateWindow(isDialog, isUserGesture); + } + + @Override + public void activateContents() { + mContentsClient.onRequestFocus(); + } + + @Override + public void updatePreferredSize(int width, int height) { + mPreferredSizeChangedListener.updatePreferredSize(width, height); + } +} 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 new file mode 100644 index 0000000..72e62a6 --- /dev/null +++ b/android_webview/javatests/src/org/chromium/android_webview/test/AndroidViewIntegrationTest.java @@ -0,0 +1,191 @@ +// 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.test.suitebuilder.annotation.SmallTest; +import android.view.View; +import android.view.View.MeasureSpec; +import android.view.ViewGroup.LayoutParams; +import android.widget.LinearLayout; +import android.util.Log; + +import org.chromium.android_webview.AwContents; +import org.chromium.android_webview.AwContentsClient; +import org.chromium.android_webview.AwLayoutSizer; +import org.chromium.android_webview.test.util.CommonResources; +import org.chromium.base.test.util.Feature; +import org.chromium.content.browser.ContentViewCore; +import org.chromium.content.browser.test.util.CallbackHelper; + +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.TimeoutException; + +/** + * Tests for certain edge cases related to integrating with the Android view system. + */ +public class AndroidViewIntegrationTest extends AwTestBase { + private static class OnContentSizeChangedHelper extends CallbackHelper { + private int mWidth; + private int mHeight; + + public int getWidth() { + assert(getCallCount() > 0); + return mWidth; + } + + public int getHeight() { + assert(getCallCount() > 0); + return mHeight; + } + + public void onContentSizeChanged(int widthCss, int heightCss) { + mWidth = widthCss; + mHeight = heightCss; + notifyCalled(); + } + } + + private OnContentSizeChangedHelper mOnContentSizeChangedHelper = + new OnContentSizeChangedHelper(); + private CallbackHelper mOnPageScaleChangedHelper = new CallbackHelper(); + + private class TestAwLayoutSizer extends AwLayoutSizer { + @Override + public void onContentSizeChanged(int widthCss, int heightCss) { + super.onContentSizeChanged(widthCss, heightCss); + mOnContentSizeChangedHelper.onContentSizeChanged(widthCss, heightCss); + } + + @Override + public void onPageScaleChanged(double pageScaleFactor) { + super.onPageScaleChanged(pageScaleFactor); + mOnPageScaleChangedHelper.notifyCalled(); + } + } + + private class DependencyFactory extends TestDependencyFactory { + @Override + public AwLayoutSizer createLayoutSizer() { + return new TestAwLayoutSizer(); + } + } + + final LinearLayout.LayoutParams wrapContentLayoutParams = + new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + + private AwTestContainerView createCustomTestContainerViewOnMainSync( + final AwContentsClient awContentsClient, final int visibility) throws Exception { + final AtomicReference<AwTestContainerView> testContainerView = + new AtomicReference<AwTestContainerView>(); + getInstrumentation().runOnMainSync(new Runnable() { + @Override + public void run() { + testContainerView.set(createAwTestContainerView(awContentsClient, + new DependencyFactory())); + testContainerView.get().setLayoutParams(wrapContentLayoutParams); + testContainerView.get().setVisibility(visibility); + } + }); + return testContainerView.get(); + } + + private AwTestContainerView createDetachedTestContainerViewOnMainSync( + final AwContentsClient awContentsClient) { + final AtomicReference<AwTestContainerView> testContainerView = + new AtomicReference<AwTestContainerView>(); + getInstrumentation().runOnMainSync(new Runnable() { + @Override + public void run() { + testContainerView.set(createDetachedAwTestContainerView(awContentsClient, + new DependencyFactory())); + } + }); + return testContainerView.get(); + } + + private void assertZeroHeight(final AwTestContainerView testContainerView) throws Throwable { + // Make sure the test isn't broken by the view having a non-zero height. + getInstrumentation().runOnMainSync(new Runnable() { + @Override + public void run() { + assertEquals(0, testContainerView.getHeight()); + } + }); + } + + /** + * This checks for issues related to loading content into a 0x0 view. + * + * A 0x0 sized view is common if the WebView is set to wrap_content and newly created. The + * expected behavior is for the WebView to expand after some content is loaded. + * In Chromium it would be valid to not load or render content into a WebContents with a 0x0 + * view (since the user can't see it anyway) and only do so after the view's size is non-zero. + * Such behavior is unacceptable for the WebView and this test is to ensure that such behavior + * is not re-introduced. + */ + @SmallTest + @Feature({"AndroidWebView"}) + public void testZeroByZeroViewLoadsContent() throws Throwable { + final TestAwContentsClient contentsClient = new TestAwContentsClient(); + final AwTestContainerView testContainerView = createCustomTestContainerViewOnMainSync( + contentsClient, View.VISIBLE); + assertZeroHeight(testContainerView); + + final int contentSizeChangeCallCount = mOnContentSizeChangedHelper.getCallCount(); + final int pageScaleChangeCallCount = mOnPageScaleChangedHelper.getCallCount(); + loadUrlAsync(testContainerView.getAwContents(), CommonResources.ABOUT_HTML); + mOnPageScaleChangedHelper.waitForCallback(pageScaleChangeCallCount); + mOnContentSizeChangedHelper.waitForCallback(contentSizeChangeCallCount); + assertTrue(mOnContentSizeChangedHelper.getHeight() > 0); + } + + /** + * Check that a content size change notification is issued when the view is invisible. + * + * This makes sure that any optimizations related to the view's visibility don't inhibit + * the ability to load pages. Many applications keep the WebView hidden when it's loading. + */ + @SmallTest + @Feature({"AndroidWebView"}) + public void testInvisibleViewLoadsContent() throws Throwable { + final TestAwContentsClient contentsClient = new TestAwContentsClient(); + final AwTestContainerView testContainerView = createCustomTestContainerViewOnMainSync( + contentsClient, View.INVISIBLE); + assertZeroHeight(testContainerView); + + final int contentSizeChangeCallCount = mOnContentSizeChangedHelper.getCallCount(); + final int pageScaleChangeCallCount = mOnPageScaleChangedHelper.getCallCount(); + loadUrlAsync(testContainerView.getAwContents(), CommonResources.ABOUT_HTML); + mOnPageScaleChangedHelper.waitForCallback(pageScaleChangeCallCount); + mOnContentSizeChangedHelper.waitForCallback(contentSizeChangeCallCount); + assertTrue(mOnContentSizeChangedHelper.getHeight() > 0); + + getInstrumentation().runOnMainSync(new Runnable() { + @Override + public void run() { + assertEquals(View.INVISIBLE, testContainerView.getVisibility()); + } + }); + } + + /** + * Check that a content size change notification is sent even if the WebView is off screen. + */ + @SmallTest + @Feature({"AndroidWebView"}) + public void testDisconnectedViewLoadsContent() throws Throwable { + final TestAwContentsClient contentsClient = new TestAwContentsClient(); + final AwTestContainerView testContainerView = + createDetachedTestContainerViewOnMainSync(contentsClient); + assertZeroHeight(testContainerView); + + final int contentSizeChangeCallCount = mOnContentSizeChangedHelper.getCallCount(); + final int pageScaleChangeCallCount = mOnPageScaleChangedHelper.getCallCount(); + loadUrlAsync(testContainerView.getAwContents(), CommonResources.ABOUT_HTML); + mOnPageScaleChangedHelper.waitForCallback(pageScaleChangeCallCount); + mOnContentSizeChangedHelper.waitForCallback(contentSizeChangeCallCount); + assertTrue(mOnContentSizeChangedHelper.getHeight() > 0); + } +} diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwLayoutSizerTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwLayoutSizerTest.java index 687e93c..88fd733 100644 --- a/android_webview/javatests/src/org/chromium/android_webview/test/AwLayoutSizerTest.java +++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwLayoutSizerTest.java @@ -31,18 +31,29 @@ public class AwLayoutSizerTest extends InstrumentationTestCase { } } - private static final int firstContentWidth = 101; - private static final int firstContentHeight = 389; - private static final int secondContentWidth = 103; - private static final int secondContentHeight = 397; + private static final int FIRST_CONTENT_WIDTH = 101; + private static final int FIRST_CONTENT_HEIGHT = 389; + private static final int SECOND_CONTENT_WIDTH = 103; + private static final int SECOND_CONTENT_HEIGHT = 397; + + private static final int SMALLER_CONTENT_SIZE = 25; + private static final int AT_MOST_MEASURE_SIZE = 50; + private static final int TOO_LARGE_CONTENT_SIZE = 100; + + private static final double INITIAL_PAGE_SCALE = 1.0; + private static final double DIP_SCALE = 1.0; public void testCanQueryContentSize() { + AwLayoutSizer layoutSizer = new AwLayoutSizer(); LayoutSizerDelegate delegate = new LayoutSizerDelegate(); - AwLayoutSizer layoutSizer = new AwLayoutSizer(delegate); + layoutSizer.setDelegate(delegate); + layoutSizer.setDIPScale(DIP_SCALE); + final int contentWidth = 101; final int contentHeight = 389; layoutSizer.onContentSizeChanged(contentWidth, contentHeight); + layoutSizer.onPageScaleChanged(INITIAL_PAGE_SCALE); layoutSizer.onMeasure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); @@ -52,47 +63,167 @@ public class AwLayoutSizerTest extends InstrumentationTestCase { } public void testContentSizeChangeRequestsLayout() { + AwLayoutSizer layoutSizer = new AwLayoutSizer(); LayoutSizerDelegate delegate = new LayoutSizerDelegate(); - AwLayoutSizer layoutSizer = new AwLayoutSizer(delegate); + layoutSizer.setDelegate(delegate); + layoutSizer.setDIPScale(DIP_SCALE); - layoutSizer.onContentSizeChanged(firstContentWidth, firstContentHeight); + layoutSizer.onContentSizeChanged(FIRST_CONTENT_WIDTH, FIRST_CONTENT_HEIGHT); + layoutSizer.onPageScaleChanged(INITIAL_PAGE_SCALE); final int requestLayoutCallCount = delegate.requestLayoutCallCount; - layoutSizer.onContentSizeChanged(secondContentWidth, secondContentWidth); + layoutSizer.onContentSizeChanged(SECOND_CONTENT_WIDTH, SECOND_CONTENT_WIDTH); assertEquals(requestLayoutCallCount + 1, delegate.requestLayoutCallCount); } public void testContentSizeChangeDoesNotRequestLayoutIfMeasuredExcatly() { + AwLayoutSizer layoutSizer = new AwLayoutSizer(); LayoutSizerDelegate delegate = new LayoutSizerDelegate(); - AwLayoutSizer layoutSizer = new AwLayoutSizer(delegate); + layoutSizer.setDelegate(delegate); + layoutSizer.setDIPScale(DIP_SCALE); - layoutSizer.onContentSizeChanged(firstContentWidth, firstContentHeight); + layoutSizer.onContentSizeChanged(FIRST_CONTENT_WIDTH, FIRST_CONTENT_HEIGHT); + layoutSizer.onPageScaleChanged(INITIAL_PAGE_SCALE); layoutSizer.onMeasure(MeasureSpec.makeMeasureSpec(50, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); final int requestLayoutCallCount = delegate.requestLayoutCallCount; - layoutSizer.onContentSizeChanged(secondContentWidth, firstContentHeight); + layoutSizer.onContentSizeChanged(SECOND_CONTENT_WIDTH, FIRST_CONTENT_HEIGHT); + + assertEquals(requestLayoutCallCount, delegate.requestLayoutCallCount); + } + + public void testDuplicateContentSizeChangeDoesNotRequestLayout() { + AwLayoutSizer layoutSizer = new AwLayoutSizer(); + LayoutSizerDelegate delegate = new LayoutSizerDelegate(); + layoutSizer.setDelegate(delegate); + layoutSizer.setDIPScale(DIP_SCALE); + + layoutSizer.onContentSizeChanged(FIRST_CONTENT_WIDTH, FIRST_CONTENT_HEIGHT); + layoutSizer.onPageScaleChanged(INITIAL_PAGE_SCALE); + layoutSizer.onMeasure(MeasureSpec.makeMeasureSpec(50, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); + final int requestLayoutCallCount = delegate.requestLayoutCallCount; + layoutSizer.onContentSizeChanged(FIRST_CONTENT_WIDTH, FIRST_CONTENT_HEIGHT); assertEquals(requestLayoutCallCount, delegate.requestLayoutCallCount); } public void testContentHeightGrowsTillAtMostSize() { + AwLayoutSizer layoutSizer = new AwLayoutSizer(); + LayoutSizerDelegate delegate = new LayoutSizerDelegate(); + layoutSizer.setDelegate(delegate); + layoutSizer.setDIPScale(DIP_SCALE); + + layoutSizer.onPageScaleChanged(INITIAL_PAGE_SCALE); + layoutSizer.onContentSizeChanged(SMALLER_CONTENT_SIZE, SMALLER_CONTENT_SIZE); + layoutSizer.onMeasure( + MeasureSpec.makeMeasureSpec(AT_MOST_MEASURE_SIZE, MeasureSpec.AT_MOST), + MeasureSpec.makeMeasureSpec(AT_MOST_MEASURE_SIZE, MeasureSpec.AT_MOST)); + assertEquals(AT_MOST_MEASURE_SIZE, delegate.measuredWidth); + assertEquals(SMALLER_CONTENT_SIZE, delegate.measuredHeight); + + layoutSizer.onContentSizeChanged(TOO_LARGE_CONTENT_SIZE, TOO_LARGE_CONTENT_SIZE); + layoutSizer.onMeasure( + MeasureSpec.makeMeasureSpec(AT_MOST_MEASURE_SIZE, MeasureSpec.AT_MOST), + MeasureSpec.makeMeasureSpec(AT_MOST_MEASURE_SIZE, MeasureSpec.AT_MOST)); + assertEquals(AT_MOST_MEASURE_SIZE, delegate.measuredWidth & View.MEASURED_SIZE_MASK); + assertEquals(AT_MOST_MEASURE_SIZE, delegate.measuredHeight & View.MEASURED_SIZE_MASK); + } + + public void testScaleChangeRequestsLayout() { + AwLayoutSizer layoutSizer = new AwLayoutSizer(); LayoutSizerDelegate delegate = new LayoutSizerDelegate(); - AwLayoutSizer layoutSizer = new AwLayoutSizer(delegate); - - final int smallerContentSize = 25; - final int atMostMeasureSize = 50; - final int tooLargeContentSize = 100; - - layoutSizer.onContentSizeChanged(smallerContentSize, smallerContentSize); - layoutSizer.onMeasure(MeasureSpec.makeMeasureSpec(atMostMeasureSize, MeasureSpec.AT_MOST), - MeasureSpec.makeMeasureSpec(atMostMeasureSize, MeasureSpec.AT_MOST)); - assertEquals(atMostMeasureSize, delegate.measuredWidth); - assertEquals(smallerContentSize, delegate.measuredHeight); - - layoutSizer.onContentSizeChanged(tooLargeContentSize, tooLargeContentSize); - layoutSizer.onMeasure(MeasureSpec.makeMeasureSpec(atMostMeasureSize, MeasureSpec.AT_MOST), - MeasureSpec.makeMeasureSpec(atMostMeasureSize, MeasureSpec.AT_MOST)); - assertEquals(atMostMeasureSize, delegate.measuredWidth & View.MEASURED_SIZE_MASK); - assertEquals(atMostMeasureSize, delegate.measuredHeight & View.MEASURED_SIZE_MASK); + layoutSizer.setDelegate(delegate); + layoutSizer.setDIPScale(DIP_SCALE); + + layoutSizer.onPageScaleChanged(INITIAL_PAGE_SCALE); + layoutSizer.onContentSizeChanged(FIRST_CONTENT_WIDTH, FIRST_CONTENT_HEIGHT); + layoutSizer.onMeasure( + MeasureSpec.makeMeasureSpec(AT_MOST_MEASURE_SIZE, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); + final int requestLayoutCallCount = delegate.requestLayoutCallCount; + layoutSizer.onPageScaleChanged(INITIAL_PAGE_SCALE + 0.5); + + assertEquals(requestLayoutCallCount + 1, delegate.requestLayoutCallCount); + } + + public void testDuplicateScaleChangeDoesNotRequestLayout() { + AwLayoutSizer layoutSizer = new AwLayoutSizer(); + LayoutSizerDelegate delegate = new LayoutSizerDelegate(); + layoutSizer.setDelegate(delegate); + layoutSizer.setDIPScale(DIP_SCALE); + + layoutSizer.onPageScaleChanged(INITIAL_PAGE_SCALE); + layoutSizer.onContentSizeChanged(FIRST_CONTENT_WIDTH, FIRST_CONTENT_HEIGHT); + layoutSizer.onMeasure(MeasureSpec.makeMeasureSpec(50, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); + final int requestLayoutCallCount = delegate.requestLayoutCallCount; + layoutSizer.onPageScaleChanged(DIP_SCALE); + + assertEquals(requestLayoutCallCount, delegate.requestLayoutCallCount); + } + + public void testScaleChangeGrowsTillAtMostSize() { + AwLayoutSizer layoutSizer = new AwLayoutSizer(); + LayoutSizerDelegate delegate = new LayoutSizerDelegate(); + layoutSizer.setDelegate(delegate); + layoutSizer.setDIPScale(DIP_SCALE); + + final double tooLargePageScale = 3.00; + + layoutSizer.onContentSizeChanged(SMALLER_CONTENT_SIZE, SMALLER_CONTENT_SIZE); + layoutSizer.onPageScaleChanged(INITIAL_PAGE_SCALE); + layoutSizer.onMeasure( + MeasureSpec.makeMeasureSpec(AT_MOST_MEASURE_SIZE, MeasureSpec.AT_MOST), + MeasureSpec.makeMeasureSpec(AT_MOST_MEASURE_SIZE, MeasureSpec.AT_MOST)); + assertEquals(AT_MOST_MEASURE_SIZE, delegate.measuredWidth); + assertEquals(SMALLER_CONTENT_SIZE, delegate.measuredHeight); + + layoutSizer.onPageScaleChanged(tooLargePageScale); + layoutSizer.onMeasure( + MeasureSpec.makeMeasureSpec(AT_MOST_MEASURE_SIZE, MeasureSpec.AT_MOST), + MeasureSpec.makeMeasureSpec(AT_MOST_MEASURE_SIZE, MeasureSpec.AT_MOST)); + assertEquals(AT_MOST_MEASURE_SIZE, delegate.measuredWidth & View.MEASURED_SIZE_MASK); + assertEquals(AT_MOST_MEASURE_SIZE, delegate.measuredHeight & View.MEASURED_SIZE_MASK); + } + + public void testFreezeAndUnfreezeDoesntCauseLayout() { + AwLayoutSizer layoutSizer = new AwLayoutSizer(); + LayoutSizerDelegate delegate = new LayoutSizerDelegate(); + layoutSizer.setDelegate(delegate); + layoutSizer.setDIPScale(DIP_SCALE); + + final int requestLayoutCallCount = delegate.requestLayoutCallCount; + layoutSizer.freezeLayoutRequests(); + layoutSizer.unfreezeLayoutRequests(); + assertEquals(requestLayoutCallCount, delegate.requestLayoutCallCount); + } + + public void testFreezeInhibitsLayoutRequest() { + AwLayoutSizer layoutSizer = new AwLayoutSizer(); + LayoutSizerDelegate delegate = new LayoutSizerDelegate(); + layoutSizer.setDelegate(delegate); + layoutSizer.setDIPScale(DIP_SCALE); + + layoutSizer.freezeLayoutRequests(); + layoutSizer.onContentSizeChanged(FIRST_CONTENT_WIDTH, FIRST_CONTENT_HEIGHT); + final int requestLayoutCallCount = delegate.requestLayoutCallCount; + layoutSizer.onContentSizeChanged(SECOND_CONTENT_WIDTH, SECOND_CONTENT_WIDTH); + assertEquals(requestLayoutCallCount, delegate.requestLayoutCallCount); + } + + public void testUnfreezeIssuesLayoutRequest() { + AwLayoutSizer layoutSizer = new AwLayoutSizer(); + LayoutSizerDelegate delegate = new LayoutSizerDelegate(); + layoutSizer.setDelegate(delegate); + layoutSizer.setDIPScale(DIP_SCALE); + + layoutSizer.freezeLayoutRequests(); + layoutSizer.onContentSizeChanged(FIRST_CONTENT_WIDTH, FIRST_CONTENT_HEIGHT); + final int requestLayoutCallCount = delegate.requestLayoutCallCount; + layoutSizer.onContentSizeChanged(SECOND_CONTENT_WIDTH, SECOND_CONTENT_WIDTH); + assertEquals(requestLayoutCallCount, delegate.requestLayoutCallCount); + layoutSizer.unfreezeLayoutRequests(); + assertEquals(requestLayoutCallCount + 1, delegate.requestLayoutCallCount); } } 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 cf59ae5..0c1d1e0 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 @@ -12,6 +12,7 @@ import org.chromium.android_webview.AwBrowserContext; import org.chromium.android_webview.AwBrowserProcess; import org.chromium.android_webview.AwContents; import org.chromium.android_webview.AwContentsClient; +import org.chromium.android_webview.AwLayoutSizer; import org.chromium.android_webview.AwSettings; import org.chromium.android_webview.test.util.JSUtils; import org.chromium.base.test.util.InMemorySharedPreferences; @@ -189,26 +190,52 @@ public class AwTestBase }); } + /** + * Factory class used in creation of test AwContents instances. + * + * Test cases can provide subclass instances to the createAwTest* methods in order to create an + * AwContents instance with injected test dependencies. + */ + public static class TestDependencyFactory { + public AwLayoutSizer createLayoutSizer() { + return new AwLayoutSizer(); + } + } + protected AwTestContainerView createAwTestContainerView( final AwContentsClient awContentsClient) { - return createAwTestContainerView(new AwTestContainerView(getActivity()), - awContentsClient); + return createAwTestContainerView(awContentsClient, new TestDependencyFactory()); } protected AwTestContainerView createAwTestContainerView( - final AwTestContainerView testContainerView, - final AwContentsClient awContentsClient) { + final AwContentsClient awContentsClient, + final TestDependencyFactory testDependencyFactory) { + AwTestContainerView testContainerView = + createDetachedAwTestContainerView(awContentsClient, testDependencyFactory); + getActivity().addView(testContainerView); + testContainerView.requestFocus(); + return testContainerView; + } + + protected AwTestContainerView createDetachedAwTestContainerView( + final AwContentsClient awContentsClient, + final TestDependencyFactory testDependencyFactory) { + final AwTestContainerView testContainerView = new AwTestContainerView(getActivity()); testContainerView.initialize(new AwContents( new AwBrowserContext(new InMemorySharedPreferences()), testContainerView, testContainerView.getInternalAccessDelegate(), - awContentsClient, false)); - getActivity().addView(testContainerView); - testContainerView.requestFocus(); + awContentsClient, false, testDependencyFactory.createLayoutSizer())); return testContainerView; } 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 6d2db78..b1b53ee 100644 --- a/android_webview/native/aw_contents.cc +++ b/android_webview/native/aw_contents.cc @@ -628,7 +628,8 @@ jint AwContents::ReleasePopupWebContents(JNIEnv* env, jobject obj) { gfx::Point AwContents::GetLocationOnScreen() { JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef<jobject> obj = java_ref_.get(env); - if (obj.is_null()) return gfx::Point(); + if (obj.is_null()) + return gfx::Point(); std::vector<int> location; base::android::JavaIntArrayToIntVector( env, @@ -637,6 +638,14 @@ gfx::Point AwContents::GetLocationOnScreen() { return gfx::Point(location[0], location[1]); } +void AwContents::OnPageScaleFactorChanged(float page_scale_factor) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jobject> obj = java_ref_.get(env); + if (obj.is_null()) + return; + Java_AwContents_onPageScaleFactorChanged(env, obj.obj(), page_scale_factor); +} + ScopedJavaLocalRef<jobject> AwContents::CapturePicture(JNIEnv* env, jobject obj) { return browser_view_renderer_->CapturePicture(); diff --git a/android_webview/native/aw_contents.h b/android_webview/native/aw_contents.h index 667e67d..5466c6f 100644 --- a/android_webview/native/aw_contents.h +++ b/android_webview/native/aw_contents.h @@ -144,6 +144,7 @@ class AwContents : public FindHelper::Listener, virtual void OnNewPicture( const base::android::JavaRef<jobject>& picture) OVERRIDE; virtual gfx::Point GetLocationOnScreen() OVERRIDE; + virtual void OnPageScaleFactorChanged(float page_scale_factor) OVERRIDE; void ClearCache(JNIEnv* env, jobject obj, jboolean include_disk_files); void SetPendingWebContentsForPopup(scoped_ptr<content::WebContents> pending); diff --git a/android_webview/native/aw_settings.cc b/android_webview/native/aw_settings.cc index 8ab91dd..81ff074 100644 --- a/android_webview/native/aw_settings.cc +++ b/android_webview/native/aw_settings.cc @@ -6,6 +6,8 @@ #include "android_webview/browser/renderer_host/aw_render_view_host_ext.h" #include "android_webview/native/aw_contents.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/web_contents.h" #include "jni/AwSettings_jni.h" #include "webkit/glue/webkit_glue.h" @@ -50,8 +52,15 @@ void AwSettings::SetTextZoom(JNIEnv* env, jobject obj, jint text_zoom_percent) { UpdateTextZoom(); } -void AwSettings::SetWebContents(JNIEnv* env, jobject obj, jint web_contents) { - Observe(reinterpret_cast<content::WebContents*>(web_contents)); +void AwSettings::SetWebContents(JNIEnv* env, jobject obj, jint jweb_contents) { + content::WebContents* web_contents = + reinterpret_cast<content::WebContents*>(jweb_contents); + Observe(web_contents); + + UpdateRenderViewHostExtSettings(); + if (web_contents->GetRenderViewHost()) { + UpdateRenderViewHostSettings(web_contents->GetRenderViewHost()); + } } void AwSettings::UpdateInitialPageScale() { @@ -76,11 +85,37 @@ void AwSettings::UpdateTextZoom() { } } -void AwSettings::RenderViewCreated(content::RenderViewHost* render_view_host) { +void AwSettings::UpdatePreferredSizeMode( + content::RenderViewHost* render_view_host) { + render_view_host->EnablePreferredSizeMode(); +} + +void AwSettings::UpdateRenderViewHostExtSettings() { UpdateInitialPageScale(); UpdateTextZoom(); } +void AwSettings::UpdateRenderViewHostSettings( + content::RenderViewHost* render_view_host) { + UpdatePreferredSizeMode(render_view_host); +} + +void AwSettings::RenderViewCreated(content::RenderViewHost* render_view_host) { + // A single WebContents can normally have 0, 1 or 2 RenderViewHost instances + // associated with it. + // This is important since there is only one RenderViewHostExt instance per + // WebContents (and not one RVHExt per RVH, as you might expect) and updating + // settings via RVHExt only ever updates the 'current' RVH. + // In android_webview we don't swap out the RVH on cross-site navigations, so + // we shouldn't have to deal with the multiple RVH per WebContents case. That + // in turn means that the newly created RVH is always the 'current' RVH + // (since we only ever go from 0 to 1 RVH instances) and hence the DCHECK. + DCHECK(web_contents()->GetRenderViewHost() == render_view_host); + + UpdateRenderViewHostExtSettings(); + UpdateRenderViewHostSettings(render_view_host); +} + static jint Init(JNIEnv* env, jobject obj, jint web_contents) { diff --git a/android_webview/native/aw_settings.h b/android_webview/native/aw_settings.h index cc0af1d..a8658c8 100644 --- a/android_webview/native/aw_settings.h +++ b/android_webview/native/aw_settings.h @@ -31,6 +31,9 @@ class AwSettings : public content::WebContentsObserver { AwRenderViewHostExt* GetAwRenderViewHostExt(); void UpdateInitialPageScale(); void UpdateTextZoom(); + void UpdatePreferredSizeMode(content::RenderViewHost* render_view_host); + void UpdateRenderViewHostExtSettings(); + void UpdateRenderViewHostSettings(content::RenderViewHost* render_view_host); // WebContentsObserver overrides: virtual void RenderViewCreated( diff --git a/android_webview/native/aw_web_contents_delegate.cc b/android_webview/native/aw_web_contents_delegate.cc index 78849a9..8c76bc4 100644 --- a/android_webview/native/aw_web_contents_delegate.cc +++ b/android_webview/native/aw_web_contents_delegate.cc @@ -122,6 +122,17 @@ void AwWebContentsDelegate::ActivateContents(content::WebContents* contents) { } } +void AwWebContentsDelegate::UpdatePreferredSize( + WebContents* web_contents, + const gfx::Size& pref_size) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env); + if (obj.is_null()) + return; + return Java_AwWebContentsDelegate_updatePreferredSize( + env, obj.obj(), pref_size.width(), pref_size.height()); +} + bool RegisterAwWebContentsDelegate(JNIEnv* env) { return RegisterNativesImpl(env); } diff --git a/android_webview/native/aw_web_contents_delegate.h b/android_webview/native/aw_web_contents_delegate.h index 56fed6b..3df57d3 100644 --- a/android_webview/native/aw_web_contents_delegate.h +++ b/android_webview/native/aw_web_contents_delegate.h @@ -40,6 +40,8 @@ class AwWebContentsDelegate bool* was_blocked) OVERRIDE; virtual void CloseContents(content::WebContents* source) OVERRIDE; virtual void ActivateContents(content::WebContents* contents) OVERRIDE; + virtual void UpdatePreferredSize(content::WebContents* web_contents, + const gfx::Size& pref_size) OVERRIDE; }; bool RegisterAwWebContentsDelegate(JNIEnv* env); diff --git a/android_webview/renderer/aw_render_view_ext.cc b/android_webview/renderer/aw_render_view_ext.cc index 2e253ef..27a7242 100644 --- a/android_webview/renderer/aw_render_view_ext.cc +++ b/android_webview/renderer/aw_render_view_ext.cc @@ -127,7 +127,7 @@ void PopulateHitTestData(const GURL& absolute_link_url, } // namespace AwRenderViewExt::AwRenderViewExt(content::RenderView* render_view) - : content::RenderViewObserver(render_view) { + : content::RenderViewObserver(render_view), page_scale_factor_(0.0f) { render_view->GetWebView()->setPermissionClient(this); } @@ -192,6 +192,18 @@ void AwRenderViewExt::DidCommitProvisionalLoad(WebKit::WebFrame* frame, } } +void AwRenderViewExt::DidCommitCompositorFrame() { + UpdatePageScaleFactor(); +} + +void AwRenderViewExt::UpdatePageScaleFactor() { + if (page_scale_factor_ != render_view()->GetWebView()->pageScaleFactor()) { + page_scale_factor_ = render_view()->GetWebView()->pageScaleFactor(); + Send(new AwViewHostMsg_PageScaleFactorChanged(routing_id(), + page_scale_factor_)); + } +} + void AwRenderViewExt::FocusedNodeChanged(const WebKit::WebNode& node) { if (node.isNull() || !node.isElementNode() || !render_view()) return; diff --git a/android_webview/renderer/aw_render_view_ext.h b/android_webview/renderer/aw_render_view_ext.h index e94bee6..1379913 100644 --- a/android_webview/renderer/aw_render_view_ext.h +++ b/android_webview/renderer/aw_render_view_ext.h @@ -36,6 +36,7 @@ class AwRenderViewExt : public content::RenderViewObserver, virtual void DidCommitProvisionalLoad(WebKit::WebFrame* frame, bool is_new_navigation) OVERRIDE; virtual void FocusedNodeChanged(const WebKit::WebNode& node) OVERRIDE; + virtual void DidCommitCompositorFrame() OVERRIDE; void OnDocumentHasImagesRequest(int id); @@ -47,6 +48,8 @@ class AwRenderViewExt : public content::RenderViewObserver, void OnSetInitialPageScale(double page_scale_factor); + void UpdatePageScaleFactor(); + // WebKit::WebPermissionClient implementation. virtual bool allowImage(WebKit::WebFrame* frame, bool enabledPerSettings, @@ -54,6 +57,8 @@ class AwRenderViewExt : public content::RenderViewObserver, bool capture_picture_enabled_; + float page_scale_factor_; + DISALLOW_COPY_AND_ASSIGN(AwRenderViewExt); }; |