1072 lines
39 KiB
Java
1072 lines
39 KiB
Java
|
/*
|
||
|
* Copyright (C) 2006 The Android Open Source Project
|
||
|
* Copyright (C) 2011, The Linux Foundation. All rights reserved.
|
||
|
*
|
||
|
* 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.app.ActivityManager;
|
||
|
import android.content.ComponentCallbacks;
|
||
|
import android.content.Context;
|
||
|
import android.content.res.AssetManager;
|
||
|
import android.content.res.Configuration;
|
||
|
import android.database.Cursor;
|
||
|
import android.graphics.Bitmap;
|
||
|
import android.net.ParseException;
|
||
|
import android.net.Uri;
|
||
|
import android.net.WebAddress;
|
||
|
import android.net.http.SslCertificate;
|
||
|
import android.os.Handler;
|
||
|
import android.os.Message;
|
||
|
import android.os.SystemProperties;
|
||
|
import android.provider.OpenableColumns;
|
||
|
import android.util.Log;
|
||
|
import android.util.TypedValue;
|
||
|
import android.view.Surface;
|
||
|
import android.view.ViewRoot;
|
||
|
import android.view.WindowManager;
|
||
|
|
||
|
import junit.framework.Assert;
|
||
|
|
||
|
import java.io.InputStream;
|
||
|
import java.lang.ref.WeakReference;
|
||
|
import java.net.URLEncoder;
|
||
|
import java.util.ArrayList;
|
||
|
import java.util.HashMap;
|
||
|
import java.util.Map;
|
||
|
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;
|
||
|
// Flag for blocking messages. This is used during destroy() so
|
||
|
// that if the UI thread posts any messages after the message
|
||
|
// queue has been cleared,they are ignored.
|
||
|
private boolean mBlockMessages = false;
|
||
|
private int mOrientation = -1;
|
||
|
|
||
|
// Is this frame the main frame?
|
||
|
private boolean mIsMainFrame;
|
||
|
|
||
|
// Attached Javascript interfaces
|
||
|
private Map<String, Object> mJSInterfaceMap;
|
||
|
|
||
|
// message ids
|
||
|
// a message posted when a frame loading is completed
|
||
|
static final int FRAME_COMPLETED = 1001;
|
||
|
// orientation change message
|
||
|
static final int ORIENTATION_CHANGED = 1002;
|
||
|
// 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;
|
||
|
|
||
|
private final PreConnectionManager mTcpPreConnectionManager;
|
||
|
|
||
|
private static class ConfigCallback implements ComponentCallbacks {
|
||
|
private final ArrayList<WeakReference<Handler>> mHandlers =
|
||
|
new ArrayList<WeakReference<Handler>>();
|
||
|
private final WindowManager mWindowManager;
|
||
|
|
||
|
ConfigCallback(WindowManager wm) {
|
||
|
mWindowManager = wm;
|
||
|
}
|
||
|
|
||
|
public synchronized void addHandler(Handler h) {
|
||
|
// No need to ever remove a Handler. If the BrowserFrame is
|
||
|
// destroyed, it will be collected and the WeakReference set to
|
||
|
// null. If it happens to still be around during a configuration
|
||
|
// change, the message will be ignored.
|
||
|
mHandlers.add(new WeakReference<Handler>(h));
|
||
|
}
|
||
|
|
||
|
public void onConfigurationChanged(Configuration newConfig) {
|
||
|
if (mHandlers.size() == 0) {
|
||
|
return;
|
||
|
}
|
||
|
int orientation =
|
||
|
mWindowManager.getDefaultDisplay().getOrientation();
|
||
|
switch (orientation) {
|
||
|
case Surface.ROTATION_90:
|
||
|
orientation = 90;
|
||
|
break;
|
||
|
case Surface.ROTATION_180:
|
||
|
orientation = 180;
|
||
|
break;
|
||
|
case Surface.ROTATION_270:
|
||
|
orientation = -90;
|
||
|
break;
|
||
|
case Surface.ROTATION_0:
|
||
|
orientation = 0;
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
synchronized (this) {
|
||
|
// Create a list of handlers to remove. Go ahead and make it
|
||
|
// the same size to avoid resizing.
|
||
|
ArrayList<WeakReference> handlersToRemove =
|
||
|
new ArrayList<WeakReference>(mHandlers.size());
|
||
|
for (WeakReference<Handler> wh : mHandlers) {
|
||
|
Handler h = wh.get();
|
||
|
if (h != null) {
|
||
|
h.sendMessage(h.obtainMessage(ORIENTATION_CHANGED,
|
||
|
orientation, 0));
|
||
|
} else {
|
||
|
handlersToRemove.add(wh);
|
||
|
}
|
||
|
}
|
||
|
// Now remove all the null references.
|
||
|
for (WeakReference weak : handlersToRemove) {
|
||
|
mHandlers.remove(weak);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void onLowMemory() {}
|
||
|
}
|
||
|
static ConfigCallback sConfigCallback;
|
||
|
|
||
|
/**
|
||
|
* 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, Map<String, Object> javascriptInterfaces) {
|
||
|
|
||
|
Context appContext = context.getApplicationContext();
|
||
|
|
||
|
// Create a global JWebCoreJavaBridge to handle timers and
|
||
|
// cookies in the WebCore thread.
|
||
|
if (sJavaBridge == null) {
|
||
|
sJavaBridge = new JWebCoreJavaBridge();
|
||
|
// set WebCore native cache size
|
||
|
ActivityManager am = (ActivityManager) context
|
||
|
.getSystemService(Context.ACTIVITY_SERVICE);
|
||
|
if (am.getMemoryClass() > 16) {
|
||
|
sJavaBridge.setCacheSize(SystemProperties
|
||
|
.getInt("net.webkit.cache.size", 8 * 1024 * 1024));
|
||
|
} else {
|
||
|
sJavaBridge.setCacheSize(4 * 1024 * 1024);
|
||
|
}
|
||
|
// initialize CacheManager
|
||
|
CacheManager.init(appContext);
|
||
|
// create CookieSyncManager with current Context
|
||
|
CookieSyncManager.createInstance(appContext);
|
||
|
// create PluginManager with current Context
|
||
|
PluginManager.getInstance(appContext);
|
||
|
}
|
||
|
|
||
|
if (sConfigCallback == null) {
|
||
|
sConfigCallback = new ConfigCallback(
|
||
|
(WindowManager) context.getSystemService(
|
||
|
Context.WINDOW_SERVICE));
|
||
|
ViewRoot.addConfigCallback(sConfigCallback);
|
||
|
}
|
||
|
sConfigCallback.addHandler(this);
|
||
|
|
||
|
mJSInterfaceMap = javascriptInterfaces;
|
||
|
|
||
|
mSettings = settings;
|
||
|
mContext = context;
|
||
|
mCallbackProxy = proxy;
|
||
|
mDatabase = WebViewDatabase.getInstance(appContext);
|
||
|
mWebViewCore = w;
|
||
|
|
||
|
mTcpPreConnectionManager = new PreConnectionManager(appContext);
|
||
|
|
||
|
AssetManager am = context.getAssets();
|
||
|
nativeCreateFrame(w, am, proxy.getBackForwardList());
|
||
|
|
||
|
if (DebugFlags.BROWSER_FRAME) {
|
||
|
Log.v(LOGTAG, "BrowserFrame constructor: this=" + this);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void startDnsPrefetch() {
|
||
|
if (DebugFlags.BROWSER_FRAME) {
|
||
|
Log.v(LOGTAG, "Starting DNS prefetch");
|
||
|
}
|
||
|
|
||
|
DnsResolver dnsResolver = DnsResolver.getInstance();
|
||
|
if(dnsResolver == null )
|
||
|
return;
|
||
|
|
||
|
HashMap hostsMap = nativeGetEmbeddedHostNames(dnsResolver.getMaxParallelDnsQueryPerPage());
|
||
|
if(hostsMap == null)
|
||
|
return;
|
||
|
|
||
|
dnsResolver.resolveDnsForHostMap(hostsMap);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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.
|
||
|
* @param extraHeaders The extra headers sent with this url. This should not
|
||
|
* include the common headers like "user-agent". If it does, it
|
||
|
* will be replaced by the intrinsic value of the WebView.
|
||
|
*/
|
||
|
public void loadUrl(String url, Map<String, String> extraHeaders) {
|
||
|
mLoadInitFromJava = true;
|
||
|
if (URLUtil.isJavaScriptUrl(url)) {
|
||
|
// strip off the scheme and evaluate the string
|
||
|
stringByEvaluatingJavaScriptFromString(
|
||
|
url.substring("javascript:".length()));
|
||
|
} else {
|
||
|
nativeLoadUrl(url, extraHeaders);
|
||
|
}
|
||
|
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
|
||
|
* historyUrl is used as the history entry for the load data.
|
||
|
*
|
||
|
* @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 historyUrl URL to use as the history entry.
|
||
|
*/
|
||
|
public void loadData(String baseUrl, String data, String mimeType,
|
||
|
String encoding, String historyUrl) {
|
||
|
mLoadInitFromJava = true;
|
||
|
if (historyUrl == null || historyUrl.length() == 0) {
|
||
|
historyUrl = "about:blank";
|
||
|
}
|
||
|
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, historyUrl);
|
||
|
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;
|
||
|
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}
|
||
|
//we pass false as the parameter paintHeader to contentDraw since here we are not painting a cached header
|
||
|
mWebViewCore.contentDraw(false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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) {
|
||
|
|
||
|
// Extracting host from url, main host will be saved
|
||
|
WebAddress uri = new WebAddress(url);
|
||
|
if (uri.mScheme.equals("http")) {
|
||
|
mTcpPreConnectionManager.onLoadStarted(uri.mHost);
|
||
|
}
|
||
|
|
||
|
// 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;
|
||
|
mWebViewCore.getWebView().mViewManager.postResetStateAll();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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) {
|
||
|
resetLoadingStates();
|
||
|
mCallbackProxy.switchOutDrawHistory();
|
||
|
mCallbackProxy.onPageFinished(url);
|
||
|
|
||
|
mTcpPreConnectionManager.onLoadFinished();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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();
|
||
|
mBlockMessages = true;
|
||
|
removeCallbacksAndMessages(null);
|
||
|
|
||
|
mTcpPreConnectionManager.onDestroy();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Handle messages posted to us.
|
||
|
* @param msg The message to handle.
|
||
|
*/
|
||
|
@Override
|
||
|
public void handleMessage(Message msg) {
|
||
|
if (mBlockMessages) {
|
||
|
return;
|
||
|
}
|
||
|
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]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
WebViewWorker.getHandler().sendEmptyMessage(
|
||
|
WebViewWorker.MSG_TRIM_CACHE);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case POLICY_FUNCTION: {
|
||
|
nativeCallPolicyFunction(msg.arg1, msg.arg2);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case ORIENTATION_CHANGED: {
|
||
|
if (mOrientation != msg.arg1) {
|
||
|
mOrientation = msg.arg1;
|
||
|
nativeOrientationChanged(msg.arg1);
|
||
|
}
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Called by JNI. Given a URI, find the associated file and return its size
|
||
|
* @param uri A String representing the URI of the desired file.
|
||
|
* @return int The size of the given file.
|
||
|
*/
|
||
|
private int getFileSize(String uri) {
|
||
|
int size = 0;
|
||
|
try {
|
||
|
InputStream stream = mContext.getContentResolver()
|
||
|
.openInputStream(Uri.parse(uri));
|
||
|
size = stream.available();
|
||
|
stream.close();
|
||
|
} catch (Exception e) {}
|
||
|
return size;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Called by JNI. Given a URI, a buffer, and an offset into the buffer,
|
||
|
* copy the resource into buffer.
|
||
|
* @param uri A String representing the URI of the desired file.
|
||
|
* @param buffer The byte array to copy the data into.
|
||
|
* @param offset The offet into buffer to place the data.
|
||
|
* @param expectedSize The size that the buffer has allocated for this file.
|
||
|
* @return int The size of the given file, or zero if it fails.
|
||
|
*/
|
||
|
private int getFile(String uri, byte[] buffer, int offset,
|
||
|
int expectedSize) {
|
||
|
int size = 0;
|
||
|
try {
|
||
|
InputStream stream = mContext.getContentResolver()
|
||
|
.openInputStream(Uri.parse(uri));
|
||
|
size = stream.available();
|
||
|
if (size <= expectedSize && buffer != null
|
||
|
&& buffer.length - offset >= size) {
|
||
|
stream.read(buffer, offset, size);
|
||
|
} else {
|
||
|
size = 0;
|
||
|
}
|
||
|
stream.close();
|
||
|
} catch (java.io.FileNotFoundException e) {
|
||
|
Log.e(LOGTAG, "FileNotFoundException:" + e);
|
||
|
size = 0;
|
||
|
} catch (java.io.IOException e2) {
|
||
|
Log.e(LOGTAG, "IOException: " + e2);
|
||
|
size = 0;
|
||
|
}
|
||
|
return size;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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 postDataIdentifier If the post data contained form this is the form identifier, otherwise it is 0.
|
||
|
* @param cacheMode The cache mode to use when loading this resource. See WebSettings.setCacheMode
|
||
|
* @param mainResource True if the this resource is the main request, not a supporting resource
|
||
|
* @param userGesture
|
||
|
* @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,
|
||
|
long postDataIdentifier,
|
||
|
int cacheMode,
|
||
|
boolean mainResource,
|
||
|
boolean userGesture,
|
||
|
boolean synchronous,
|
||
|
String username,
|
||
|
String password,
|
||
|
int priority,
|
||
|
boolean commit) {
|
||
|
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 (DebugFlags.BROWSER_FRAME) {
|
||
|
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(!mainResource) {
|
||
|
WebAddress uri = new WebAddress(url);
|
||
|
if (uri.mScheme.equals("http")) {
|
||
|
mTcpPreConnectionManager.onResourceLoad(uri.mHost);
|
||
|
}else {
|
||
|
if (uri.mScheme.equals("")) {
|
||
|
mTcpPreConnectionManager.onResourceLoad("");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (DebugFlags.BROWSER_FRAME) {
|
||
|
Log.v(LOGTAG, "startLoadingResource: url=" + url + ", method="
|
||
|
+ method + ", postData=" + postData + ", isMainFramePage="
|
||
|
+ isMainFramePage + ", mainResource=" + mainResource
|
||
|
+ ", userGesture=" + userGesture);
|
||
|
}
|
||
|
|
||
|
// Create a LoadListener
|
||
|
LoadListener loadListener = LoadListener.getLoadListener(mContext,
|
||
|
this, url, loaderHandle, synchronous, isMainFramePage,
|
||
|
mainResource, userGesture, postDataIdentifier, username, password);
|
||
|
loadListener.setPriority(priority);
|
||
|
loadListener.setShouldCommit(commit);
|
||
|
|
||
|
mCallbackProxy.onLoadResource(url);
|
||
|
|
||
|
if (LoadListener.getNativeLoaderCount() > MAX_OUTSTANDING_REQUESTS) {
|
||
|
// send an error message, so that loadListener can be deleted
|
||
|
// after this is returned. This is important as LoadListener's
|
||
|
// nativeError will remove the request from its DocLoader's request
|
||
|
// list. But the set up is not done until this method is returned.
|
||
|
loadListener.error(
|
||
|
android.net.http.EventHandler.ERROR, mContext.getString(
|
||
|
com.android.internal.R.string.httpErrorTooManyRequests));
|
||
|
return loadListener;
|
||
|
}
|
||
|
|
||
|
FrameLoader loader = new FrameLoader(loadListener, mSettings, method);
|
||
|
loader.setHeaders(headers);
|
||
|
loader.setPostData(postData);
|
||
|
// Set the load mode to the mode used for the current page.
|
||
|
// If WebKit wants validation, go to network directly.
|
||
|
loader.setCacheMode(headers.containsKey("If-Modified-Since")
|
||
|
|| headers.containsKey("If-None-Match") ?
|
||
|
WebSettings.LOAD_NO_CACHE : cacheMode);
|
||
|
// Set referrer to current URL?
|
||
|
if (!loader.executeLoad()) {
|
||
|
checker.responseAlert("startLoadingResource fail");
|
||
|
}
|
||
|
checker.responseAlert("startLoadingResource succeed");
|
||
|
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
// Called by JNI when an apple-touch-icon attribute was found.
|
||
|
private void didReceiveTouchIconUrl(String url, boolean precomposed) {
|
||
|
mCallbackProxy.onReceivedTouchIconUrl(url, precomposed);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Request a new window from the client.
|
||
|
* @return The BrowserFrame object stored in the new WebView.
|
||
|
*/
|
||
|
private BrowserFrame createWindow(boolean dialog, boolean userGesture) {
|
||
|
return mCallbackProxy.createWindow(dialog, userGesture);
|
||
|
}
|
||
|
|
||
|
private void resolveDnsForHost(String host) {
|
||
|
if(DnsResolver.getInstance() != null)
|
||
|
DnsResolver.getInstance().resolveDnsForHost(host,"1");
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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 rawResId in PlatformBridge.h
|
||
|
private static final int NODOMAIN = 1;
|
||
|
private static final int LOADERROR = 2;
|
||
|
private static final int DRAWABLEDIR = 3;
|
||
|
private static final int FILE_UPLOAD_LABEL = 4;
|
||
|
private static final int RESET_LABEL = 5;
|
||
|
private static final int SUBMIT_LABEL = 6;
|
||
|
|
||
|
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;
|
||
|
|
||
|
case DRAWABLEDIR:
|
||
|
// use one known resource to find the drawable directory
|
||
|
resid = com.android.internal.R.drawable.btn_check_off;
|
||
|
break;
|
||
|
|
||
|
case FILE_UPLOAD_LABEL:
|
||
|
return mContext.getResources().getString(
|
||
|
com.android.internal.R.string.upload_file);
|
||
|
|
||
|
case RESET_LABEL:
|
||
|
return mContext.getResources().getString(
|
||
|
com.android.internal.R.string.reset);
|
||
|
|
||
|
case SUBMIT_LABEL:
|
||
|
return mContext.getResources().getString(
|
||
|
com.android.internal.R.string.submit);
|
||
|
|
||
|
default:
|
||
|
Log.e(LOGTAG, "getRawResFilename got incompatible resource ID");
|
||
|
return "";
|
||
|
}
|
||
|
TypedValue value = new TypedValue();
|
||
|
mContext.getResources().getValue(resid, value, true);
|
||
|
if (id == DRAWABLEDIR) {
|
||
|
String path = value.string.toString();
|
||
|
int index = path.lastIndexOf('/');
|
||
|
if (index < 0) {
|
||
|
Log.e(LOGTAG, "Can't find drawable directory.");
|
||
|
return "";
|
||
|
}
|
||
|
return path.substring(0, index + 1);
|
||
|
}
|
||
|
return value.string.toString();
|
||
|
}
|
||
|
|
||
|
private float density() {
|
||
|
return mContext.getResources().getDisplayMetrics().density;
|
||
|
}
|
||
|
|
||
|
//==========================================================================
|
||
|
// 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, Map<String, String> headers);
|
||
|
|
||
|
private native void nativePostUrl(String url, byte[] postData);
|
||
|
|
||
|
private native void nativeLoadData(String baseUrl, String data,
|
||
|
String mimeType, String encoding, String historyUrl);
|
||
|
|
||
|
private native HashMap nativeGetEmbeddedHostNames(int maxDnsHostCount);
|
||
|
|
||
|
/**
|
||
|
* Stop loading the current page.
|
||
|
*/
|
||
|
public void stopLoading() {
|
||
|
if (mIsMainFrame) {
|
||
|
resetLoadingStates();
|
||
|
}
|
||
|
nativeStopLoading();
|
||
|
}
|
||
|
|
||
|
private native void nativeStopLoading();
|
||
|
|
||
|
/**
|
||
|
* 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();
|
||
|
|
||
|
private native void nativeOrientationChanged(int orientation);
|
||
|
}
|