/* * Copyright (C) 2006 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.content.Context; import android.content.res.AssetManager; import android.graphics.Bitmap; import android.net.ParseException; import android.net.WebAddress; import android.net.http.SslCertificate; import android.os.Handler; import android.os.Message; import android.util.Log; import android.util.TypedValue; import junit.framework.Assert; import java.net.URLEncoder; import java.util.HashMap; import java.util.Iterator; class BrowserFrame extends Handler { private static final String LOGTAG = "webkit"; /** * Cap the number of LoadListeners that will be instantiated, so * we don't blow the GREF count. Attempting to queue more than * this many requests will prompt an error() callback on the * request's LoadListener */ private final static int MAX_OUTSTANDING_REQUESTS = 300; private final CallbackProxy mCallbackProxy; private final WebSettings mSettings; private final Context mContext; private final WebViewDatabase mDatabase; private final WebViewCore mWebViewCore; /* package */ boolean mLoadInitFromJava; private int mLoadType; private boolean mFirstLayoutDone = true; private boolean mCommitted = true; // Is this frame the main frame? private boolean mIsMainFrame; // Attached Javascript interfaces private HashMap mJSInterfaceMap; // message ids // a message posted when a frame loading is completed static final int FRAME_COMPLETED = 1001; // a message posted when the user decides the policy static final int POLICY_FUNCTION = 1003; // Note: need to keep these in sync with FrameLoaderTypes.h in native static final int FRAME_LOADTYPE_STANDARD = 0; static final int FRAME_LOADTYPE_BACK = 1; static final int FRAME_LOADTYPE_FORWARD = 2; static final int FRAME_LOADTYPE_INDEXEDBACKFORWARD = 3; static final int FRAME_LOADTYPE_RELOAD = 4; static final int FRAME_LOADTYPE_RELOADALLOWINGSTALEDATA = 5; static final int FRAME_LOADTYPE_SAME = 6; static final int FRAME_LOADTYPE_REDIRECT = 7; static final int FRAME_LOADTYPE_REPLACE = 8; // A progress threshold to switch from history Picture to live Picture private static final int TRANSITION_SWITCH_THRESHOLD = 75; // This is a field accessed by native code as well as package classes. /*package*/ int mNativeFrame; // Static instance of a JWebCoreJavaBridge to handle timer and cookie // requests from WebCore. static JWebCoreJavaBridge sJavaBridge; /** * Create a new BrowserFrame to be used in an application. * @param context An application context to use when retrieving assets. * @param w A WebViewCore used as the view for this frame. * @param proxy A CallbackProxy for posting messages to the UI thread and * querying a client for information. * @param settings A WebSettings object that holds all settings. * XXX: Called by WebCore thread. */ public BrowserFrame(Context context, WebViewCore w, CallbackProxy proxy, WebSettings settings) { // Create a global JWebCoreJavaBridge to handle timers and // cookies in the WebCore thread. if (sJavaBridge == null) { sJavaBridge = new JWebCoreJavaBridge(); // set WebCore native cache size sJavaBridge.setCacheSize(4 * 1024 * 1024); // initialize CacheManager CacheManager.init(context); // create CookieSyncManager with current Context CookieSyncManager.createInstance(context); // create PluginManager with current Context PluginManager.getInstance(context); } mSettings = settings; mContext = context; mCallbackProxy = proxy; mDatabase = WebViewDatabase.getInstance(context); mWebViewCore = w; AssetManager am = context.getAssets(); nativeCreateFrame(w, am, proxy.getBackForwardList()); if (DebugFlags.BROWSER_FRAME) { Log.v(LOGTAG, "BrowserFrame constructor: this=" + this); } } /** * Load a url from the network or the filesystem into the main frame. * Following the same behaviour as Safari, javascript: URLs are not * passed to the main frame, instead they are evaluated immediately. * @param url The url to load. */ public void loadUrl(String url) { mLoadInitFromJava = true; if (URLUtil.isJavaScriptUrl(url)) { // strip off the scheme and evaluate the string stringByEvaluatingJavaScriptFromString( url.substring("javascript:".length())); } else { nativeLoadUrl(url); } mLoadInitFromJava = false; } /** * Load a url with "POST" method from the network into the main frame. * @param url The url to load. * @param data The data for POST request. */ public void postUrl(String url, byte[] data) { mLoadInitFromJava = true; nativePostUrl(url, data); mLoadInitFromJava = false; } /** * Load the content as if it was loaded by the provided base URL. The * failUrl is used as the history entry for the load data. If null or * an empty string is passed for the failUrl, then no history entry is * created. * * @param baseUrl Base URL used to resolve relative paths in the content * @param data Content to render in the browser * @param mimeType Mimetype of the data being passed in * @param encoding Character set encoding of the provided data. * @param failUrl URL to use if the content fails to load or null. */ public void loadData(String baseUrl, String data, String mimeType, String encoding, String failUrl) { mLoadInitFromJava = true; if (failUrl == null) { failUrl = ""; } if (data == null) { data = ""; } // Setup defaults for missing values. These defaults where taken from // WebKit's WebFrame.mm if (baseUrl == null || baseUrl.length() == 0) { baseUrl = "about:blank"; } if (mimeType == null || mimeType.length() == 0) { mimeType = "text/html"; } nativeLoadData(baseUrl, data, mimeType, encoding, failUrl); mLoadInitFromJava = false; } /** * Go back or forward the number of steps given. * @param steps A negative or positive number indicating the direction * and number of steps to move. */ public void goBackOrForward(int steps) { mLoadInitFromJava = true; nativeGoBackOrForward(steps); mLoadInitFromJava = false; } /** * native callback * Report an error to an activity. * @param errorCode The HTTP error code. * @param description A String description. * TODO: Report all errors including resource errors but include some kind * of domain identifier. Change errorCode to an enum for a cleaner * interface. */ private void reportError(final int errorCode, final String description, final String failingUrl) { // As this is called for the main resource and loading will be stopped // after, reset the state variables. resetLoadingStates(); mCallbackProxy.onReceivedError(errorCode, description, failingUrl); } private void resetLoadingStates() { mCommitted = true; mWebViewCore.mEndScaleZoom = mFirstLayoutDone == false; mFirstLayoutDone = true; } /* package */boolean committed() { return mCommitted; } /* package */boolean firstLayoutDone() { return mFirstLayoutDone; } /* package */int loadType() { return mLoadType; } /* package */void didFirstLayout() { if (!mFirstLayoutDone) { mFirstLayoutDone = true; // ensure {@link WebViewCore#webkitDraw} is called as we were // blocking the update in {@link #loadStarted} mWebViewCore.contentDraw(); } mWebViewCore.mEndScaleZoom = true; } /** * native callback * Indicates the beginning of a new load. * This method will be called once for the main frame. */ private void loadStarted(String url, Bitmap favicon, int loadType, boolean isMainFrame) { mIsMainFrame = isMainFrame; if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) { mLoadType = loadType; if (isMainFrame) { // Call onPageStarted for main frames. mCallbackProxy.onPageStarted(url, favicon); // as didFirstLayout() is only called for the main frame, reset // mFirstLayoutDone only for the main frames mFirstLayoutDone = false; mCommitted = false; // remove pending draw to block update until mFirstLayoutDone is // set to true in didFirstLayout() mWebViewCore.removeMessages(WebViewCore.EventHub.WEBKIT_DRAW); } // Note: only saves committed form data in standard load if (loadType == FRAME_LOADTYPE_STANDARD && mSettings.getSaveFormData()) { final WebHistoryItem h = mCallbackProxy.getBackForwardList() .getCurrentItem(); if (h != null) { String currentUrl = h.getUrl(); if (currentUrl != null) { mDatabase.setFormData(currentUrl, getFormTextData()); } } } } } /** * native callback * Indicates the WebKit has committed to the new load */ private void transitionToCommitted(int loadType, boolean isMainFrame) { // loadType is not used yet if (isMainFrame) { mCommitted = true; } } /** * native callback *
* Indicates the end of a new load.
* This method will be called once for the main frame.
*/
private void loadFinished(String url, int loadType, boolean isMainFrame) {
// mIsMainFrame and isMainFrame are better be equal!!!
if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) {
if (isMainFrame) {
resetLoadingStates();
mCallbackProxy.switchOutDrawHistory();
mCallbackProxy.onPageFinished(url);
}
}
}
/**
* We have received an SSL certificate for the main top-level page.
*
* !!!Called from the network thread!!!
*/
void certificate(SslCertificate certificate) {
if (mIsMainFrame) {
// we want to make this call even if the certificate is null
// (ie, the site is not secure)
mCallbackProxy.onReceivedCertificate(certificate);
}
}
/**
* Destroy all native components of the BrowserFrame.
*/
public void destroy() {
nativeDestroyFrame();
removeCallbacksAndMessages(null);
}
/**
* Handle messages posted to us.
* @param msg The message to handle.
*/
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case FRAME_COMPLETED: {
if (mSettings.getSavePassword() && hasPasswordField()) {
WebHistoryItem item = mCallbackProxy.getBackForwardList()
.getCurrentItem();
if (item != null) {
WebAddress uri = new WebAddress(item.getUrl());
String schemePlusHost = uri.mScheme + uri.mHost;
String[] up =
mDatabase.getUsernamePassword(schemePlusHost);
if (up != null && up[0] != null) {
setUsernamePassword(up[0], up[1]);
}
}
}
CacheManager.trimCacheIfNeeded();
break;
}
case POLICY_FUNCTION: {
nativeCallPolicyFunction(msg.arg1, msg.arg2);
break;
}
default:
break;
}
}
/**
* Punch-through for WebCore to set the document
* title. Inform the Activity of the new title.
* @param title The new title of the document.
*/
private void setTitle(String title) {
// FIXME: The activity must call getTitle (a native method) to get the
// title. We should try and cache the title if we can also keep it in
// sync with the document.
mCallbackProxy.onReceivedTitle(title);
}
/**
* Retrieves the render tree of this frame and puts it as the object for
* the message and sends the message.
* @param callback the message to use to send the render tree
*/
public void externalRepresentation(Message callback) {
callback.obj = externalRepresentation();;
callback.sendToTarget();
}
/**
* Return the render tree as a string
*/
private native String externalRepresentation();
/**
* Retrieves the visual text of the current frame, puts it as the object for
* the message and sends the message.
* @param callback the message to use to send the visual text
*/
public void documentAsText(Message callback) {
callback.obj = documentAsText();;
callback.sendToTarget();
}
/**
* Return the text drawn on the screen as a string
*/
private native String documentAsText();
/*
* This method is called by WebCore to inform the frame that
* the Javascript window object has been cleared.
* We should re-attach any attached js interfaces.
*/
private void windowObjectCleared(int nativeFramePointer) {
if (mJSInterfaceMap != null) {
Iterator iter = mJSInterfaceMap.keySet().iterator();
while (iter.hasNext()) {
String interfaceName = (String) iter.next();
nativeAddJavascriptInterface(nativeFramePointer,
mJSInterfaceMap.get(interfaceName), interfaceName);
}
}
}
/**
* This method is called by WebCore to check whether application
* wants to hijack url loading
*/
public boolean handleUrl(String url) {
if (mLoadInitFromJava == true) {
return false;
}
if (mCallbackProxy.shouldOverrideUrlLoading(url)) {
// if the url is hijacked, reset the state of the BrowserFrame
didFirstLayout();
return true;
} else {
return false;
}
}
public void addJavascriptInterface(Object obj, String interfaceName) {
if (mJSInterfaceMap == null) {
mJSInterfaceMap = new HashMap