From 816c0defc73b6604a12c3f409b24ca3823897af5 Mon Sep 17 00:00:00 2001 From: John Reck Date: Thu, 2 Jun 2011 16:04:53 -0700 Subject: [PATCH] Initial support for view state serialization Framework for serializing the view state Still needs to prevent sending messages to webkit (pinch zoom doesn't work correctly as a result) Change-Id: Ic3f8fe19b27ff1f841b556e87f582dab2a6d903b --- .../android/webkit/ViewStateSerializer.java | 80 ++++++++ core/java/android/webkit/WebView.java | 171 +++++++++++------- 2 files changed, 190 insertions(+), 61 deletions(-) create mode 100644 core/java/android/webkit/ViewStateSerializer.java diff --git a/core/java/android/webkit/ViewStateSerializer.java b/core/java/android/webkit/ViewStateSerializer.java new file mode 100644 index 0000000000000..81f9e7004bf2e --- /dev/null +++ b/core/java/android/webkit/ViewStateSerializer.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.webkit; + +import android.graphics.Point; +import android.graphics.Rect; +import android.graphics.Region; +import android.webkit.WebViewCore.DrawData; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * @hide + */ +class ViewStateSerializer { + + private static final int WORKING_STREAM_STORAGE = 16 * 1024; + + static final int VERSION = 1; + + static boolean serializeViewState(OutputStream stream, WebView web) + throws IOException { + DataOutputStream dos = new DataOutputStream(stream); + dos.writeInt(VERSION); + dos.writeInt(web.getContentWidth()); + dos.writeInt(web.getContentHeight()); + return nativeSerializeViewState(web.getBaseLayer(), dos, + new byte[WORKING_STREAM_STORAGE]); + } + + static DrawData deserializeViewState(InputStream stream, WebView web) + throws IOException { + DataInputStream dis = new DataInputStream(stream); + int version = dis.readInt(); + if (version != VERSION) { + throw new IOException("Unexpected version: " + version); + } + int contentWidth = dis.readInt(); + int contentHeight = dis.readInt(); + int baseLayer = nativeDeserializeViewState(dis, + new byte[WORKING_STREAM_STORAGE]); + + final WebViewCore.DrawData draw = new WebViewCore.DrawData(); + draw.mViewState = new WebViewCore.ViewState(); + int viewWidth = web.getViewWidth(); + int viewHeight = web.getViewHeightWithTitle() - web.getTitleHeight(); + draw.mViewSize = new Point(viewWidth, viewHeight); + draw.mContentSize = new Point(contentWidth, contentHeight); + draw.mViewState.mDefaultScale = web.getDefaultZoomScale(); + draw.mBaseLayer = baseLayer; + draw.mInvalRegion = new Region(0, 0, contentWidth, contentHeight); + return draw; + } + + private static native boolean nativeSerializeViewState(int baseLayer, + OutputStream stream, byte[] storage); + + // Returns a pointer to the BaseLayer + private static native int nativeDeserializeViewState( + InputStream stream, byte[] storage); + + private ViewStateSerializer() {} +} diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 3c2c8f667b0a4..ade198db29c22 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -84,6 +84,7 @@ import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.webkit.WebTextView.AutoCompleteAdapter; +import android.webkit.WebViewCore.DrawData; import android.webkit.WebViewCore.EventHub; import android.webkit.WebViewCore.TouchEventData; import android.webkit.WebViewCore.TouchHighlightData; @@ -102,6 +103,9 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.net.URLDecoder; import java.util.ArrayList; import java.util.HashMap; @@ -1400,7 +1404,7 @@ public class WebView extends AbsoluteLayout return getViewHeightWithTitle() - getVisibleTitleHeight(); } - private int getViewHeightWithTitle() { + int getViewHeightWithTitle() { int height = getHeight(); if (isHorizontalScrollBarEnabled() && !mOverlayHorizontalScrollbar) { height -= getHorizontalScrollbarHeight(); @@ -1785,6 +1789,42 @@ public class WebView extends AbsoluteLayout return true; } + /** + * Saves the view data to the output stream. The output is highly + * version specific, and may not be able to be loaded by newer versions + * of WebView. + * @param stream The {@link OutputStream} to save to + * @return True if saved successfully + * @hide + */ + public boolean saveViewState(OutputStream stream) { + try { + return ViewStateSerializer.serializeViewState(stream, this); + } catch (IOException e) { + Log.w(LOGTAG, "Failed to saveViewState", e); + } + return false; + } + + /** + * Loads the view data from the input stream. See + * {@link #saveViewState(OutputStream)} for more information. + * @param stream The {@link InputStream} to load from + * @return True if loaded successfully + * @hide + */ + public boolean loadViewState(InputStream stream) { + try { + mWebViewCore.sendMessage(EventHub.CLEAR_CONTENT); + DrawData draw = ViewStateSerializer.deserializeViewState(stream, this); + setNewPicture(draw); + return true; + } catch (IOException e) { + Log.w(LOGTAG, "Failed to loadViewState", e); + } + return false; + } + /** * Restore the state of this WebView from the given map used in * {@link android.app.Activity#onRestoreInstanceState}. This method should @@ -4192,6 +4232,10 @@ public class WebView extends AbsoluteLayout } } + int getBaseLayer() { + return nativeGetBaseLayer(); + } + private void onZoomAnimationStart() { // If it is in password mode, turn it off so it does not draw misplaced. if (inEditingMode() && nativeFocusCandidateIsPassword()) { @@ -7966,66 +8010,7 @@ public class WebView extends AbsoluteLayout case NEW_PICTURE_MSG_ID: { // called for new content final WebViewCore.DrawData draw = (WebViewCore.DrawData) msg.obj; - WebViewCore.ViewState viewState = draw.mViewState; - boolean isPictureAfterFirstLayout = viewState != null; - setBaseLayer(draw.mBaseLayer, draw.mInvalRegion, - getSettings().getShowVisualIndicator(), - isPictureAfterFirstLayout); - final Point viewSize = draw.mViewSize; - if (isPictureAfterFirstLayout) { - // Reset the last sent data here since dealing with new page. - mLastWidthSent = 0; - mZoomManager.onFirstLayout(draw); - if (!mDrawHistory) { - // Do not send the scroll event for this particular - // scroll message. Note that a scroll event may - // still be fired if the user scrolls before the - // message can be handled. - mSendScrollEvent = false; - setContentScrollTo(viewState.mScrollX, viewState.mScrollY); - mSendScrollEvent = true; - - // As we are on a new page, remove the WebTextView. This - // is necessary for page loads driven by webkit, and in - // particular when the user was on a password field, so - // the WebTextView was visible. - clearTextEntry(); - } - } - - // We update the layout (i.e. request a layout from the - // view system) if the last view size that we sent to - // WebCore matches the view size of the picture we just - // received in the fixed dimension. - final boolean updateLayout = viewSize.x == mLastWidthSent - && viewSize.y == mLastHeightSent; - // Don't send scroll event for picture coming from webkit, - // since the new picture may cause a scroll event to override - // the saved history scroll position. - mSendScrollEvent = false; - recordNewContentSize(draw.mContentSize.x, - draw.mContentSize.y, updateLayout); - mSendScrollEvent = true; - if (DebugFlags.WEB_VIEW) { - Rect b = draw.mInvalRegion.getBounds(); - Log.v(LOGTAG, "NEW_PICTURE_MSG_ID {" + - b.left+","+b.top+","+b.right+","+b.bottom+"}"); - } - invalidateContentRect(draw.mInvalRegion.getBounds()); - - if (mPictureListener != null) { - mPictureListener.onNewPicture(WebView.this, capturePicture()); - } - - // update the zoom information based on the new picture - mZoomManager.onNewPicture(draw); - - if (draw.mFocusSizeChanged && inEditingMode()) { - mFocusSizeChanged = true; - } - if (isPictureAfterFirstLayout) { - mViewManager.postReadyToDrawAll(); - } + setNewPicture(draw); break; } case WEBCORE_INITIALIZED_MSG_ID: @@ -8345,6 +8330,69 @@ public class WebView extends AbsoluteLayout } } + void setNewPicture(final WebViewCore.DrawData draw) { + WebViewCore.ViewState viewState = draw.mViewState; + boolean isPictureAfterFirstLayout = viewState != null; + setBaseLayer(draw.mBaseLayer, draw.mInvalRegion, + getSettings().getShowVisualIndicator(), + isPictureAfterFirstLayout); + final Point viewSize = draw.mViewSize; + if (isPictureAfterFirstLayout) { + // Reset the last sent data here since dealing with new page. + mLastWidthSent = 0; + mZoomManager.onFirstLayout(draw); + if (!mDrawHistory) { + // Do not send the scroll event for this particular + // scroll message. Note that a scroll event may + // still be fired if the user scrolls before the + // message can be handled. + mSendScrollEvent = false; + setContentScrollTo(viewState.mScrollX, viewState.mScrollY); + mSendScrollEvent = true; + + // As we are on a new page, remove the WebTextView. This + // is necessary for page loads driven by webkit, and in + // particular when the user was on a password field, so + // the WebTextView was visible. + clearTextEntry(); + } + } + + // We update the layout (i.e. request a layout from the + // view system) if the last view size that we sent to + // WebCore matches the view size of the picture we just + // received in the fixed dimension. + final boolean updateLayout = viewSize.x == mLastWidthSent + && viewSize.y == mLastHeightSent; + // Don't send scroll event for picture coming from webkit, + // since the new picture may cause a scroll event to override + // the saved history scroll position. + mSendScrollEvent = false; + recordNewContentSize(draw.mContentSize.x, + draw.mContentSize.y, updateLayout); + mSendScrollEvent = true; + if (DebugFlags.WEB_VIEW) { + Rect b = draw.mInvalRegion.getBounds(); + Log.v(LOGTAG, "NEW_PICTURE_MSG_ID {" + + b.left+","+b.top+","+b.right+","+b.bottom+"}"); + } + invalidateContentRect(draw.mInvalRegion.getBounds()); + + if (mPictureListener != null) { + mPictureListener.onNewPicture(WebView.this, capturePicture()); + } + + // update the zoom information based on the new picture + mZoomManager.onNewPicture(draw); + + if (draw.mFocusSizeChanged && inEditingMode()) { + mFocusSizeChanged = true; + } + if (isPictureAfterFirstLayout) { + mViewManager.postReadyToDrawAll(); + } + } + /** * Used when receiving messages for REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID * and UPDATE_TEXT_SELECTION_MSG_ID. Update the selection of WebTextView. @@ -9047,6 +9095,7 @@ public class WebView extends AbsoluteLayout private native void nativeSetHeightCanMeasure(boolean measure); private native void nativeSetBaseLayer(int layer, Region invalRegion, boolean showVisualIndicator, boolean isPictureAfterFirstLayout); + private native int nativeGetBaseLayer(); private native void nativeShowCursorTimed(); private native void nativeReplaceBaseContent(int content); private native void nativeCopyBaseContentToPicture(Picture pict);