diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:31:44 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:31:44 -0800 |
commit | 9066cfe9886ac131c34d59ed0e2d287b0e3c0087 (patch) | |
tree | d88beb88001f2482911e3d28e43833b50e4b4e97 /core/java/android/webkit/BrowserFrame.java | |
parent | d83a98f4ce9cfa908f5c54bbd70f03eec07e7553 (diff) | |
download | frameworks_base-9066cfe9886ac131c34d59ed0e2d287b0e3c0087.zip frameworks_base-9066cfe9886ac131c34d59ed0e2d287b0e3c0087.tar.gz frameworks_base-9066cfe9886ac131c34d59ed0e2d287b0e3c0087.tar.bz2 |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'core/java/android/webkit/BrowserFrame.java')
-rw-r--r-- | core/java/android/webkit/BrowserFrame.java | 786 |
1 files changed, 786 insertions, 0 deletions
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java new file mode 100644 index 0000000..451af6d --- /dev/null +++ b/core/java/android/webkit/BrowserFrame.java @@ -0,0 +1,786 @@ +/* + * 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.Config; +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); + } + AssetManager am = context.getAssets(); + nativeCreateFrame(w, am, proxy.getBackForwardList()); + + mSettings = settings; + mContext = context; + mCallbackProxy = proxy; + mDatabase = WebViewDatabase.getInstance(context); + mWebViewCore = w; + + if (Config.LOGV) { + 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 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. + mCommitted = true; + mWebViewCore.mEndScaleZoom = mFirstLayoutDone == false; + mFirstLayoutDone = true; + mCallbackProxy.onReceivedError(errorCode, description, failingUrl); + } + + /* 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 + * <p> + * 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) { + 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()) { + if (Config.DEBUG) { + Assert.assertNotNull(mCallbackProxy.getBackForwardList() + .getCurrentItem()); + } + WebAddress uri = new WebAddress( + mCallbackProxy.getBackForwardList().getCurrentItem() + .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<String, Object>(); + } + if (mJSInterfaceMap.containsKey(interfaceName)) { + mJSInterfaceMap.remove(interfaceName); + } + mJSInterfaceMap.put(interfaceName, obj); + } + + /** + * Start loading a resource. + * @param loaderHandle The native ResourceLoader that is the target of the + * data. + * @param url The url to load. + * @param method The http method. + * @param headers The http headers. + * @param postData If the method is "POST" postData is sent as the request + * body. Is null when empty. + * @param cacheMode The cache mode to use when loading this resource. + * @param isHighPriority True if this resource needs to be put at the front + * of the network queue. + * @param synchronous True if the load is synchronous. + * @return A newly created LoadListener object. + */ + private LoadListener startLoadingResource(int loaderHandle, + String url, + String method, + HashMap headers, + byte[] postData, + int cacheMode, + boolean isHighPriority, + boolean synchronous) { + PerfChecker checker = new PerfChecker(); + + if (mSettings.getCacheMode() != WebSettings.LOAD_DEFAULT) { + cacheMode = mSettings.getCacheMode(); + } + + if (method.equals("POST")) { + // Don't use the cache on POSTs when issuing a normal POST + // request. + if (cacheMode == WebSettings.LOAD_NORMAL) { + cacheMode = WebSettings.LOAD_NO_CACHE; + } + if (mSettings.getSavePassword() && hasPasswordField()) { + try { + if (Config.DEBUG) { + Assert.assertNotNull(mCallbackProxy.getBackForwardList() + .getCurrentItem()); + } + WebAddress uri = new WebAddress(mCallbackProxy + .getBackForwardList().getCurrentItem().getUrl()); + String schemePlusHost = uri.mScheme + uri.mHost; + String[] ret = getUsernamePassword(); + // Has the user entered a username/password pair and is + // there some POST data + if (ret != null && postData != null && + ret[0].length() > 0 && ret[1].length() > 0) { + // Check to see if the username & password appear in + // the post data (there could be another form on the + // page and that was posted instead. + String postString = new String(postData); + if (postString.contains(URLEncoder.encode(ret[0])) && + postString.contains(URLEncoder.encode(ret[1]))) { + String[] saved = mDatabase.getUsernamePassword( + schemePlusHost); + if (saved != null) { + // null username implies that user has chosen not to + // save password + if (saved[0] != null) { + // non-null username implies that user has + // chosen to save password, so update the + // recorded password + mDatabase.setUsernamePassword( + schemePlusHost, ret[0], ret[1]); + } + } else { + // CallbackProxy will handle creating the resume + // message + mCallbackProxy.onSavePassword(schemePlusHost, ret[0], + ret[1], null); + } + } + } + } catch (ParseException ex) { + // if it is bad uri, don't save its password + } + + } + } + + // is this resource the main-frame top-level page? + boolean isMainFramePage = mIsMainFrame; + + if (Config.LOGV) { + Log.v(LOGTAG, "startLoadingResource: url=" + url + ", method=" + + method + ", postData=" + postData + ", isHighPriority=" + + isHighPriority + ", isMainFramePage=" + isMainFramePage); + } + + // Create a LoadListener + LoadListener loadListener = LoadListener.getLoadListener(mContext, this, url, + loaderHandle, synchronous, isMainFramePage); + + mCallbackProxy.onLoadResource(url); + + if (LoadListener.getNativeLoaderCount() > MAX_OUTSTANDING_REQUESTS) { + loadListener.error( + android.net.http.EventHandler.ERROR, mContext.getString( + com.android.internal.R.string.httpErrorTooManyRequests)); + loadListener.notifyError(); + loadListener.tearDown(); + return null; + } + + // during synchronous load, the WebViewCore thread is blocked, so we + // need to endCacheTransaction first so that http thread won't be + // blocked in setupFile() when createCacheFile. + if (synchronous) { + CacheManager.endCacheTransaction(); + } + + FrameLoader loader = new FrameLoader(loadListener, mSettings, + method, isHighPriority); + loader.setHeaders(headers); + loader.setPostData(postData); + loader.setCacheMode(cacheMode); // Set the load mode to the mode used + // for the current page. + // Set referrer to current URL? + if (!loader.executeLoad()) { + checker.responseAlert("startLoadingResource fail"); + } + checker.responseAlert("startLoadingResource succeed"); + + if (synchronous) { + CacheManager.startCacheTransaction(); + } + + return !synchronous ? loadListener : null; + } + + /** + * Set the progress for the browser activity. Called by native code. + * Uses a delay so it does not happen too often. + * @param newProgress An int between zero and one hundred representing + * the current progress percentage of loading the page. + */ + private void setProgress(int newProgress) { + mCallbackProxy.onProgressChanged(newProgress); + if (newProgress == 100) { + sendMessageDelayed(obtainMessage(FRAME_COMPLETED), 100); + } + // FIXME: Need to figure out a better way to switch out of the history + // drawing mode. Maybe we can somehow compare the history picture with + // the current picture, and switch when it contains more content. + if (mFirstLayoutDone && newProgress > TRANSITION_SWITCH_THRESHOLD) { + mCallbackProxy.switchOutDrawHistory(); + } + } + + /** + * Send the icon to the activity for display. + * @param icon A Bitmap representing a page's favicon. + */ + private void didReceiveIcon(Bitmap icon) { + mCallbackProxy.onReceivedIcon(icon); + } + + /** + * Request a new window from the client. + * @return The BrowserFrame object stored in the new WebView. + */ + private BrowserFrame createWindow(boolean dialog, boolean userGesture) { + WebView w = mCallbackProxy.createWindow(dialog, userGesture); + if (w != null) { + return w.getWebViewCore().getBrowserFrame(); + } + return null; + } + + /** + * Try to focus this WebView. + */ + private void requestFocus() { + mCallbackProxy.onRequestFocus(); + } + + /** + * Close this frame and window. + */ + private void closeWindow(WebViewCore w) { + mCallbackProxy.onCloseWindow(w.getWebView()); + } + + // XXX: Must match PolicyAction in FrameLoaderTypes.h in webcore + static final int POLICY_USE = 0; + static final int POLICY_IGNORE = 2; + + private void decidePolicyForFormResubmission(int policyFunction) { + Message dontResend = obtainMessage(POLICY_FUNCTION, policyFunction, + POLICY_IGNORE); + Message resend = obtainMessage(POLICY_FUNCTION, policyFunction, + POLICY_USE); + mCallbackProxy.onFormResubmission(dontResend, resend); + } + + /** + * Tell the activity to update its global history. + */ + private void updateVisitedHistory(String url, boolean isReload) { + mCallbackProxy.doUpdateVisitedHistory(url, isReload); + } + + /** + * Get the CallbackProxy for sending messages to the UI thread. + */ + /* package */ CallbackProxy getCallbackProxy() { + return mCallbackProxy; + } + + /** + * Returns the User Agent used by this frame + */ + String getUserAgentString() { + return mSettings.getUserAgentString(); + } + + // these ids need to be in sync with enum RAW_RES_ID in WebFrame + private static final int NODOMAIN = 1; + private static final int LOADERROR = 2; + + String getRawResFilename(int id) { + int resid; + switch (id) { + case NODOMAIN: + resid = com.android.internal.R.raw.nodomain; + break; + + case LOADERROR: + resid = com.android.internal.R.raw.loaderror; + break; + + default: + Log.e(LOGTAG, "getRawResFilename got incompatible resource ID"); + return new String(); + } + TypedValue value = new TypedValue(); + mContext.getResources().getValue(resid, value, true); + return value.string.toString(); + } + + //========================================================================== + // native functions + //========================================================================== + + /** + * Create a new native frame for a given WebView + * @param w A WebView that the frame draws into. + * @param am AssetManager to use to get assets. + * @param list The native side will add and remove items from this list as + * the native list changes. + */ + private native void nativeCreateFrame(WebViewCore w, AssetManager am, + WebBackForwardList list); + + /** + * Destroy the native frame. + */ + public native void nativeDestroyFrame(); + + private native void nativeCallPolicyFunction(int policyFunction, + int decision); + + /** + * Reload the current main frame. + */ + public native void reload(boolean allowStale); + + /** + * 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. + */ + private native void nativeGoBackOrForward(int steps); + + /** + * stringByEvaluatingJavaScriptFromString will execute the + * JS passed in in the context of this browser frame. + * @param script A javascript string to execute + * + * @return string result of execution or null + */ + public native String stringByEvaluatingJavaScriptFromString(String script); + + /** + * Add a javascript interface to the main frame. + */ + private native void nativeAddJavascriptInterface(int nativeFramePointer, + Object obj, String interfaceName); + + /** + * Enable or disable the native cache. + */ + /* FIXME: The native cache is always on for now until we have a better + * solution for our 2 caches. */ + private native void setCacheDisabled(boolean disabled); + + public native boolean cacheDisabled(); + + public native void clearCache(); + + /** + * Returns false if the url is bad. + */ + private native void nativeLoadUrl(String url); + + private native void nativeLoadData(String baseUrl, String data, + String mimeType, String encoding, String failUrl); + + /** + * Stop loading the current page. + */ + public native void stopLoading(); + + /** + * Return true if the document has images. + */ + public native boolean documentHasImages(); + + /** + * @return TRUE if there is a password field in the current frame + */ + private native boolean hasPasswordField(); + + /** + * Get username and password in the current frame. If found, String[0] is + * username and String[1] is password. Otherwise return NULL. + * @return String[] + */ + private native String[] getUsernamePassword(); + + /** + * Set username and password to the proper fields in the current frame + * @param username + * @param password + */ + private native void setUsernamePassword(String username, String password); + + /** + * Get form's "text" type data associated with the current frame. + * @return HashMap If succeed, returns a list of name/value pair. Otherwise + * returns null. + */ + private native HashMap getFormTextData(); +} |