// Copyright (c) 2012 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.util.Pair; import android.view.View.MeasureSpec; import android.view.View; import org.chromium.content.browser.ContentViewCore; /** * Helper methods used to manage the layout of the View that contains AwContents. */ public class AwLayoutSizer { // These are used to prevent a re-layout if the content size changes within a dimension that is // fixed by the view system. private boolean mWidthMeasurementIsFixed; private boolean mHeightMeasurementIsFixed; // 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. private Delegate mDelegate; public interface Delegate { void requestLayout(); void setMeasuredDimension(int measuredWidth, int measuredHeight); } /** * 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 CSS pixels. */ public void onContentSizeChanged(int widthCss, int heightCss) { doUpdate(widthCss, heightCss, mPageScaleFactor); } /** * 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) { if (mFreezeLayoutRequests) { mFrozenLayoutRequestPending = true; } else { mDelegate.requestLayout(); } } } /** * Calculate the size of the view. * This is designed to be used to implement the android.view.View#onMeasure() method. */ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); 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 // exceeded the maximum size specified by the parent. // 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 && contentHeightPix > heightSize); if (!mHeightMeasurementIsFixed) { measuredHeight = contentHeightPix; } if (!mWidthMeasurementIsFixed) { measuredWidth = contentWidthPix; } if (measuredHeight < contentHeightPix) { measuredHeight |= View.MEASURED_STATE_TOO_SMALL; } if (measuredWidth < contentWidthPix) { measuredWidth |= View.MEASURED_STATE_TOO_SMALL; } mDelegate.setMeasuredDimension(measuredWidth, measuredHeight); } }