276 lines
12 KiB
Java
Executable File
276 lines
12 KiB
Java
Executable File
// Copyright 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.view.View;
|
|
import android.view.View.MeasureSpec;
|
|
|
|
/**
|
|
* Helper methods used to manage the layout of the View that contains AwContents.
|
|
*/
|
|
public class AwLayoutSizer {
|
|
public static final int FIXED_LAYOUT_HEIGHT = 0;
|
|
|
|
// 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 float mPageScaleFactor = 0.0f;
|
|
// The page scale factor that was used in the most recent onMeasure call.
|
|
private float mLastMeasuredPageScaleFactor = 0.0f;
|
|
|
|
// 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;
|
|
|
|
// Was our height larger than the AT_MOST constraint the last time onMeasure was called?
|
|
private boolean mHeightMeasurementLimited;
|
|
// If mHeightMeasurementLimited is true then this contains the height limit.
|
|
private int mHeightMeasurementLimit;
|
|
|
|
// The most recent width and height seen in onSizeChanged.
|
|
private int mLastWidth;
|
|
private int mLastHeight;
|
|
|
|
// Used to prevent sending multiple setFixedLayoutSize notifications with the same values.
|
|
private int mLastSentFixedLayoutSizeWidth = -1;
|
|
private int mLastSentFixedLayoutSizeHeight = -1;
|
|
|
|
// Callback object for interacting with the View.
|
|
private Delegate mDelegate;
|
|
|
|
public interface Delegate {
|
|
void requestLayout();
|
|
void setMeasuredDimension(int measuredWidth, int measuredHeight);
|
|
void setFixedLayoutSize(int widthDip, int heightDip);
|
|
boolean isLayoutParamsHeightWrapContent();
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/**
|
|
* 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(float pageScaleFactor) {
|
|
doUpdate(mContentWidthCss, mContentHeightCss, pageScaleFactor);
|
|
}
|
|
|
|
private void doUpdate(int widthCss, int heightCss, float 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'.
|
|
final int heightPix = (int) (heightCss * mPageScaleFactor * mDIPScale);
|
|
boolean pageScaleChanged = mPageScaleFactor != pageScaleFactor;
|
|
boolean contentHeightChangeMeaningful = !mHeightMeasurementIsFixed &&
|
|
(!mHeightMeasurementLimited || heightPix < mHeightMeasurementLimit);
|
|
boolean pageScaleChangeMeaningful =
|
|
!mWidthMeasurementIsFixed || contentHeightChangeMeaningful;
|
|
boolean layoutNeeded = (mContentWidthCss != widthCss && !mWidthMeasurementIsFixed) ||
|
|
(mContentHeightCss != heightCss && contentHeightChangeMeaningful) ||
|
|
(pageScaleChanged && pageScaleChangeMeaningful);
|
|
|
|
mContentWidthCss = widthCss;
|
|
mContentHeightCss = heightCss;
|
|
mPageScaleFactor = pageScaleFactor;
|
|
|
|
if (layoutNeeded) {
|
|
if (mFreezeLayoutRequests) {
|
|
mFrozenLayoutRequestPending = true;
|
|
} else {
|
|
mDelegate.requestLayout();
|
|
}
|
|
} else if (pageScaleChanged && mLastWidth != 0) {
|
|
// Because the fixed layout size is directly impacted by the pageScaleFactor we must
|
|
// update it even if the physical size of the view doesn't change.
|
|
updateFixedLayoutSize(mLastWidth, mLastHeight, mPageScaleFactor);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 contentHeightPix = (int) (mContentHeightCss * mPageScaleFactor * mDIPScale);
|
|
int contentWidthPix = (int) (mContentWidthCss * mPageScaleFactor * mDIPScale);
|
|
|
|
int measuredHeight = contentHeightPix;
|
|
int measuredWidth = contentWidthPix;
|
|
|
|
mLastMeasuredPageScaleFactor = mPageScaleFactor;
|
|
|
|
// Always use the given size unless unspecified. This matches WebViewClassic behavior.
|
|
mWidthMeasurementIsFixed = (widthMode != MeasureSpec.UNSPECIFIED);
|
|
mHeightMeasurementIsFixed = (heightMode == MeasureSpec.EXACTLY);
|
|
mHeightMeasurementLimited =
|
|
(heightMode == MeasureSpec.AT_MOST) && (contentHeightPix > heightSize);
|
|
mHeightMeasurementLimit = heightSize;
|
|
|
|
if (mHeightMeasurementIsFixed || mHeightMeasurementLimited) {
|
|
measuredHeight = heightSize;
|
|
}
|
|
|
|
if (mWidthMeasurementIsFixed) {
|
|
measuredWidth = widthSize;
|
|
}
|
|
|
|
if (measuredHeight < contentHeightPix) {
|
|
measuredHeight |= View.MEASURED_STATE_TOO_SMALL;
|
|
}
|
|
|
|
if (measuredWidth < contentWidthPix) {
|
|
measuredWidth |= View.MEASURED_STATE_TOO_SMALL;
|
|
}
|
|
|
|
mDelegate.setMeasuredDimension(measuredWidth, measuredHeight);
|
|
}
|
|
|
|
/**
|
|
* Notify the AwLayoutSizer that the size of the view has changed.
|
|
* This should be called by the Android view system after onMeasure if the view's size has
|
|
* changed.
|
|
*/
|
|
public void onSizeChanged(int w, int h, int ow, int oh) {
|
|
mLastWidth = w;
|
|
mLastHeight = h;
|
|
updateFixedLayoutSize(mLastWidth, mLastHeight, mLastMeasuredPageScaleFactor);
|
|
}
|
|
|
|
/**
|
|
* Notify the AwLayoutSizer that the layout pass requested via Delegate.requestLayout has
|
|
* completed.
|
|
* This should be called after onSizeChanged regardless of whether the size has changed or not.
|
|
*/
|
|
public void onLayoutChange() {
|
|
updateFixedLayoutSize(mLastWidth, mLastHeight, mLastMeasuredPageScaleFactor);
|
|
}
|
|
|
|
private void setFixedLayoutSize(int widthDip, int heightDip) {
|
|
if (widthDip == mLastSentFixedLayoutSizeWidth &&
|
|
heightDip == mLastSentFixedLayoutSizeHeight)
|
|
return;
|
|
mLastSentFixedLayoutSizeWidth = widthDip;
|
|
mLastSentFixedLayoutSizeHeight = heightDip;
|
|
|
|
mDelegate.setFixedLayoutSize(widthDip, heightDip);
|
|
}
|
|
|
|
// This needs to be called every time either the physical size of the view is changed or the
|
|
// pageScale is changed. Since we need to ensure that this is called immediately after
|
|
// onSizeChanged we can't just wait for onLayoutChange. At the same time we can't only make this
|
|
// call from onSizeChanged, since onSizeChanged won't fire if the view's physical size doesn't
|
|
// change.
|
|
private void updateFixedLayoutSize(int w, int h, float pageScaleFactor) {
|
|
boolean wrapContentForHeight = mDelegate.isLayoutParamsHeightWrapContent();
|
|
// If the WebView's size in the Android view system depends on the size of its contents then
|
|
// the viewport size cannot be directly calculated from the WebView's physical size as that
|
|
// can result in the layout being unstable (for example loading the following contents
|
|
// <div style="height:150%">a</a>
|
|
// would cause the WebView to indefinitely attempt to increase its height by 50%).
|
|
// If both the width and height are fixed (specified by the parent View) then content size
|
|
// changes will not cause subsequent layout passes and so we don't need to do anything
|
|
// special.
|
|
// We assume the width is 'fixed' if the parent View specified an EXACT or an AT_MOST
|
|
// measureSpec for the width (in which case the AT_MOST upper bound is the width).
|
|
// That means that the WebView will ignore LayoutParams.width set to WRAP_CONTENT and will
|
|
// instead try to take up as much width as possible. This is necessary because it's not
|
|
// practical to do web layout without a set width.
|
|
// For height the behavior is different because for a given width it is possible to
|
|
// calculate the minimum height required to display all of the content. As such the WebView
|
|
// can size itself vertically to match the content height. Because certain container views
|
|
// (LinearLayout with a WRAP_CONTENT height, for example) can result in onMeasure calls with
|
|
// both EXACTLY and AT_MOST height measureSpecs it is not possible to infer the sizing
|
|
// policy for the whole subtree based on the parameters passed to the onMeasure call.
|
|
// For that reason the LayoutParams.height property of the WebView is used. This behaves
|
|
// more predictably and means that toggling the fixedLayoutSize mode (which can have
|
|
// significant impact on how the web contents is laid out) is a direct consequence of the
|
|
// developer's choice. The downside is that it could result in the Android layout being
|
|
// unstable if a parent of the WebView has a wrap_content height while the WebView itself
|
|
// has height set to match_parent. Unfortunately addressing this edge case is costly so it
|
|
// will have to stay as is (this is compatible with Classic behavior).
|
|
if ((mWidthMeasurementIsFixed && !wrapContentForHeight) || pageScaleFactor == 0) {
|
|
setFixedLayoutSize(0, 0);
|
|
return;
|
|
}
|
|
|
|
final double dipAndPageScale = pageScaleFactor * mDIPScale;
|
|
final int contentWidthPix = (int) (mContentWidthCss * dipAndPageScale);
|
|
|
|
int widthDip = (int) Math.ceil(w / dipAndPageScale);
|
|
|
|
// Make sure that we don't introduce rounding errors if the viewport is to be exactly as
|
|
// wide as the contents.
|
|
if (w == contentWidthPix) {
|
|
widthDip = mContentWidthCss;
|
|
}
|
|
|
|
// This is workaround due to the fact that in wrap content mode we need to use a fixed
|
|
// layout size independent of view height, otherwise things like <div style="height:120%">
|
|
// cause the webview to grow indefinitely. We need to use a height independent of the
|
|
// webview's height. 0 is the value used in WebViewClassic.
|
|
setFixedLayoutSize(widthDip, FIXED_LAYOUT_HEIGHT);
|
|
}
|
|
}
|