Home | History | Annotate | Download | only in webkit
      1 /*
      2  * Copyright (C) 2006 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.webkit;
     18 
     19 import android.app.ActivityManager;
     20 import android.content.ComponentCallbacks;
     21 import android.content.Context;
     22 import android.content.res.AssetManager;
     23 import android.content.res.Configuration;
     24 import android.database.Cursor;
     25 import android.graphics.Bitmap;
     26 import android.net.ParseException;
     27 import android.net.Uri;
     28 import android.net.WebAddress;
     29 import android.net.http.SslCertificate;
     30 import android.os.Handler;
     31 import android.os.Message;
     32 import android.provider.OpenableColumns;
     33 import android.util.Log;
     34 import android.util.TypedValue;
     35 import android.view.Surface;
     36 import android.view.ViewRoot;
     37 import android.view.WindowManager;
     38 
     39 import junit.framework.Assert;
     40 
     41 import java.io.InputStream;
     42 import java.lang.ref.WeakReference;
     43 import java.net.URLEncoder;
     44 import java.util.ArrayList;
     45 import java.util.HashMap;
     46 import java.util.Map;
     47 import java.util.Iterator;
     48 
     49 class BrowserFrame extends Handler {
     50 
     51     private static final String LOGTAG = "webkit";
     52 
     53     /**
     54      * Cap the number of LoadListeners that will be instantiated, so
     55      * we don't blow the GREF count.  Attempting to queue more than
     56      * this many requests will prompt an error() callback on the
     57      * request's LoadListener
     58      */
     59     private final static int MAX_OUTSTANDING_REQUESTS = 300;
     60 
     61     private final CallbackProxy mCallbackProxy;
     62     private final WebSettings mSettings;
     63     private final Context mContext;
     64     private final WebViewDatabase mDatabase;
     65     private final WebViewCore mWebViewCore;
     66     /* package */ boolean mLoadInitFromJava;
     67     private int mLoadType;
     68     private boolean mFirstLayoutDone = true;
     69     private boolean mCommitted = true;
     70     // Flag for blocking messages. This is used during destroy() so
     71     // that if the UI thread posts any messages after the message
     72     // queue has been cleared,they are ignored.
     73     private boolean mBlockMessages = false;
     74     private int mOrientation = -1;
     75 
     76     // Is this frame the main frame?
     77     private boolean mIsMainFrame;
     78 
     79     // Attached Javascript interfaces
     80     private Map<String, Object> mJSInterfaceMap;
     81 
     82     // message ids
     83     // a message posted when a frame loading is completed
     84     static final int FRAME_COMPLETED = 1001;
     85     // orientation change message
     86     static final int ORIENTATION_CHANGED = 1002;
     87     // a message posted when the user decides the policy
     88     static final int POLICY_FUNCTION = 1003;
     89 
     90     // Note: need to keep these in sync with FrameLoaderTypes.h in native
     91     static final int FRAME_LOADTYPE_STANDARD = 0;
     92     static final int FRAME_LOADTYPE_BACK = 1;
     93     static final int FRAME_LOADTYPE_FORWARD = 2;
     94     static final int FRAME_LOADTYPE_INDEXEDBACKFORWARD = 3;
     95     static final int FRAME_LOADTYPE_RELOAD = 4;
     96     static final int FRAME_LOADTYPE_RELOADALLOWINGSTALEDATA = 5;
     97     static final int FRAME_LOADTYPE_SAME = 6;
     98     static final int FRAME_LOADTYPE_REDIRECT = 7;
     99     static final int FRAME_LOADTYPE_REPLACE = 8;
    100 
    101     // A progress threshold to switch from history Picture to live Picture
    102     private static final int TRANSITION_SWITCH_THRESHOLD = 75;
    103 
    104     // This is a field accessed by native code as well as package classes.
    105     /*package*/ int mNativeFrame;
    106 
    107     // Static instance of a JWebCoreJavaBridge to handle timer and cookie
    108     // requests from WebCore.
    109     static JWebCoreJavaBridge sJavaBridge;
    110 
    111     private static class ConfigCallback implements ComponentCallbacks {
    112         private final ArrayList<WeakReference<Handler>> mHandlers =
    113                 new ArrayList<WeakReference<Handler>>();
    114         private final WindowManager mWindowManager;
    115 
    116         ConfigCallback(WindowManager wm) {
    117             mWindowManager = wm;
    118         }
    119 
    120         public synchronized void addHandler(Handler h) {
    121             // No need to ever remove a Handler. If the BrowserFrame is
    122             // destroyed, it will be collected and the WeakReference set to
    123             // null. If it happens to still be around during a configuration
    124             // change, the message will be ignored.
    125             mHandlers.add(new WeakReference<Handler>(h));
    126         }
    127 
    128         public void onConfigurationChanged(Configuration newConfig) {
    129             if (mHandlers.size() == 0) {
    130                 return;
    131             }
    132             int orientation =
    133                     mWindowManager.getDefaultDisplay().getOrientation();
    134             switch (orientation) {
    135                 case Surface.ROTATION_90:
    136                     orientation = 90;
    137                     break;
    138                 case Surface.ROTATION_180:
    139                     orientation = 180;
    140                     break;
    141                 case Surface.ROTATION_270:
    142                     orientation = -90;
    143                     break;
    144                 case Surface.ROTATION_0:
    145                     orientation = 0;
    146                     break;
    147                 default:
    148                     break;
    149             }
    150             synchronized (this) {
    151                 // Create a list of handlers to remove. Go ahead and make it
    152                 // the same size to avoid resizing.
    153                 ArrayList<WeakReference> handlersToRemove =
    154                         new ArrayList<WeakReference>(mHandlers.size());
    155                 for (WeakReference<Handler> wh : mHandlers) {
    156                     Handler h = wh.get();
    157                     if (h != null) {
    158                         h.sendMessage(h.obtainMessage(ORIENTATION_CHANGED,
    159                                     orientation, 0));
    160                     } else {
    161                         handlersToRemove.add(wh);
    162                     }
    163                 }
    164                 // Now remove all the null references.
    165                 for (WeakReference weak : handlersToRemove) {
    166                     mHandlers.remove(weak);
    167                 }
    168             }
    169         }
    170 
    171         public void onLowMemory() {}
    172     }
    173     static ConfigCallback sConfigCallback;
    174 
    175     /**
    176      * Create a new BrowserFrame to be used in an application.
    177      * @param context An application context to use when retrieving assets.
    178      * @param w A WebViewCore used as the view for this frame.
    179      * @param proxy A CallbackProxy for posting messages to the UI thread and
    180      *              querying a client for information.
    181      * @param settings A WebSettings object that holds all settings.
    182      * XXX: Called by WebCore thread.
    183      */
    184     public BrowserFrame(Context context, WebViewCore w, CallbackProxy proxy,
    185             WebSettings settings, Map<String, Object> javascriptInterfaces) {
    186 
    187         Context appContext = context.getApplicationContext();
    188 
    189         // Create a global JWebCoreJavaBridge to handle timers and
    190         // cookies in the WebCore thread.
    191         if (sJavaBridge == null) {
    192             sJavaBridge = new JWebCoreJavaBridge();
    193             // set WebCore native cache size
    194             ActivityManager am = (ActivityManager) context
    195                     .getSystemService(Context.ACTIVITY_SERVICE);
    196             if (am.getMemoryClass() > 16) {
    197                 sJavaBridge.setCacheSize(8 * 1024 * 1024);
    198             } else {
    199                 sJavaBridge.setCacheSize(4 * 1024 * 1024);
    200             }
    201             // initialize CacheManager
    202             CacheManager.init(appContext);
    203             // create CookieSyncManager with current Context
    204             CookieSyncManager.createInstance(appContext);
    205             // create PluginManager with current Context
    206             PluginManager.getInstance(appContext);
    207         }
    208 
    209         if (sConfigCallback == null) {
    210             sConfigCallback = new ConfigCallback(
    211                     (WindowManager) context.getSystemService(
    212                             Context.WINDOW_SERVICE));
    213             ViewRoot.addConfigCallback(sConfigCallback);
    214         }
    215         sConfigCallback.addHandler(this);
    216 
    217         mJSInterfaceMap = javascriptInterfaces;
    218 
    219         mSettings = settings;
    220         mContext = context;
    221         mCallbackProxy = proxy;
    222         mDatabase = WebViewDatabase.getInstance(appContext);
    223         mWebViewCore = w;
    224 
    225         AssetManager am = context.getAssets();
    226         nativeCreateFrame(w, am, proxy.getBackForwardList());
    227 
    228         if (DebugFlags.BROWSER_FRAME) {
    229             Log.v(LOGTAG, "BrowserFrame constructor: this=" + this);
    230         }
    231     }
    232 
    233     /**
    234      * Load a url from the network or the filesystem into the main frame.
    235      * Following the same behaviour as Safari, javascript: URLs are not passed
    236      * to the main frame, instead they are evaluated immediately.
    237      * @param url The url to load.
    238      * @param extraHeaders The extra headers sent with this url. This should not
    239      *            include the common headers like "user-agent". If it does, it
    240      *            will be replaced by the intrinsic value of the WebView.
    241      */
    242     public void loadUrl(String url, Map<String, String> extraHeaders) {
    243         mLoadInitFromJava = true;
    244         if (URLUtil.isJavaScriptUrl(url)) {
    245             // strip off the scheme and evaluate the string
    246             stringByEvaluatingJavaScriptFromString(
    247                     url.substring("javascript:".length()));
    248         } else {
    249             nativeLoadUrl(url, extraHeaders);
    250         }
    251         mLoadInitFromJava = false;
    252     }
    253 
    254     /**
    255      * Load a url with "POST" method from the network into the main frame.
    256      * @param url The url to load.
    257      * @param data The data for POST request.
    258      */
    259     public void postUrl(String url, byte[] data) {
    260         mLoadInitFromJava = true;
    261         nativePostUrl(url, data);
    262         mLoadInitFromJava = false;
    263     }
    264 
    265     /**
    266      * Load the content as if it was loaded by the provided base URL. The
    267      * historyUrl is used as the history entry for the load data.
    268      *
    269      * @param baseUrl Base URL used to resolve relative paths in the content
    270      * @param data Content to render in the browser
    271      * @param mimeType Mimetype of the data being passed in
    272      * @param encoding Character set encoding of the provided data.
    273      * @param historyUrl URL to use as the history entry.
    274      */
    275     public void loadData(String baseUrl, String data, String mimeType,
    276             String encoding, String historyUrl) {
    277         mLoadInitFromJava = true;
    278         if (historyUrl == null || historyUrl.length() == 0) {
    279             historyUrl = "about:blank";
    280         }
    281         if (data == null) {
    282             data = "";
    283         }
    284 
    285         // Setup defaults for missing values. These defaults where taken from
    286         // WebKit's WebFrame.mm
    287         if (baseUrl == null || baseUrl.length() == 0) {
    288             baseUrl = "about:blank";
    289         }
    290         if (mimeType == null || mimeType.length() == 0) {
    291             mimeType = "text/html";
    292         }
    293         nativeLoadData(baseUrl, data, mimeType, encoding, historyUrl);
    294         mLoadInitFromJava = false;
    295     }
    296 
    297     /**
    298      * Go back or forward the number of steps given.
    299      * @param steps A negative or positive number indicating the direction
    300      *              and number of steps to move.
    301      */
    302     public void goBackOrForward(int steps) {
    303         mLoadInitFromJava = true;
    304         nativeGoBackOrForward(steps);
    305         mLoadInitFromJava = false;
    306     }
    307 
    308     /**
    309      * native callback
    310      * Report an error to an activity.
    311      * @param errorCode The HTTP error code.
    312      * @param description A String description.
    313      * TODO: Report all errors including resource errors but include some kind
    314      * of domain identifier. Change errorCode to an enum for a cleaner
    315      * interface.
    316      */
    317     private void reportError(final int errorCode, final String description,
    318             final String failingUrl) {
    319         // As this is called for the main resource and loading will be stopped
    320         // after, reset the state variables.
    321         resetLoadingStates();
    322         mCallbackProxy.onReceivedError(errorCode, description, failingUrl);
    323     }
    324 
    325     private void resetLoadingStates() {
    326         mCommitted = true;
    327         mFirstLayoutDone = true;
    328     }
    329 
    330     /* package */boolean committed() {
    331         return mCommitted;
    332     }
    333 
    334     /* package */boolean firstLayoutDone() {
    335         return mFirstLayoutDone;
    336     }
    337 
    338     /* package */int loadType() {
    339         return mLoadType;
    340     }
    341 
    342     /* package */void didFirstLayout() {
    343         if (!mFirstLayoutDone) {
    344             mFirstLayoutDone = true;
    345             // ensure {@link WebViewCore#webkitDraw} is called as we were
    346             // blocking the update in {@link #loadStarted}
    347             mWebViewCore.contentDraw();
    348         }
    349     }
    350 
    351     /**
    352      * native callback
    353      * Indicates the beginning of a new load.
    354      * This method will be called once for the main frame.
    355      */
    356     private void loadStarted(String url, Bitmap favicon, int loadType,
    357             boolean isMainFrame) {
    358         mIsMainFrame = isMainFrame;
    359 
    360         if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) {
    361             mLoadType = loadType;
    362 
    363             if (isMainFrame) {
    364                 // Call onPageStarted for main frames.
    365                 mCallbackProxy.onPageStarted(url, favicon);
    366                 // as didFirstLayout() is only called for the main frame, reset
    367                 // mFirstLayoutDone only for the main frames
    368                 mFirstLayoutDone = false;
    369                 mCommitted = false;
    370                 // remove pending draw to block update until mFirstLayoutDone is
    371                 // set to true in didFirstLayout()
    372                 mWebViewCore.removeMessages(WebViewCore.EventHub.WEBKIT_DRAW);
    373             }
    374 
    375             // Note: only saves committed form data in standard load
    376             if (loadType == FRAME_LOADTYPE_STANDARD
    377                     && mSettings.getSaveFormData()) {
    378                 final WebHistoryItem h = mCallbackProxy.getBackForwardList()
    379                         .getCurrentItem();
    380                 if (h != null) {
    381                     String currentUrl = h.getUrl();
    382                     if (currentUrl != null) {
    383                         mDatabase.setFormData(currentUrl, getFormTextData());
    384                     }
    385                 }
    386             }
    387         }
    388     }
    389 
    390     /**
    391      * native callback
    392      * Indicates the WebKit has committed to the new load
    393      */
    394     private void transitionToCommitted(int loadType, boolean isMainFrame) {
    395         // loadType is not used yet
    396         if (isMainFrame) {
    397             mCommitted = true;
    398             mWebViewCore.getWebView().mViewManager.postResetStateAll();
    399         }
    400     }
    401 
    402     /**
    403      * native callback
    404      * <p>
    405      * Indicates the end of a new load.
    406      * This method will be called once for the main frame.
    407      */
    408     private void loadFinished(String url, int loadType, boolean isMainFrame) {
    409         // mIsMainFrame and isMainFrame are better be equal!!!
    410 
    411         if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) {
    412             if (isMainFrame) {
    413                 resetLoadingStates();
    414                 mCallbackProxy.switchOutDrawHistory();
    415                 mCallbackProxy.onPageFinished(url);
    416             }
    417         }
    418     }
    419 
    420     /**
    421      * We have received an SSL certificate for the main top-level page.
    422      *
    423      * !!!Called from the network thread!!!
    424      */
    425     void certificate(SslCertificate certificate) {
    426         if (mIsMainFrame) {
    427             // we want to make this call even if the certificate is null
    428             // (ie, the site is not secure)
    429             mCallbackProxy.onReceivedCertificate(certificate);
    430         }
    431     }
    432 
    433     /**
    434      * Destroy all native components of the BrowserFrame.
    435      */
    436     public void destroy() {
    437         nativeDestroyFrame();
    438         mBlockMessages = true;
    439         removeCallbacksAndMessages(null);
    440     }
    441 
    442     /**
    443      * Handle messages posted to us.
    444      * @param msg The message to handle.
    445      */
    446     @Override
    447     public void handleMessage(Message msg) {
    448         if (mBlockMessages) {
    449             return;
    450         }
    451         switch (msg.what) {
    452             case FRAME_COMPLETED: {
    453                 if (mSettings.getSavePassword() && hasPasswordField()) {
    454                     WebHistoryItem item = mCallbackProxy.getBackForwardList()
    455                             .getCurrentItem();
    456                     if (item != null) {
    457                         WebAddress uri = new WebAddress(item.getUrl());
    458                         String schemePlusHost = uri.mScheme + uri.mHost;
    459                         String[] up =
    460                                 mDatabase.getUsernamePassword(schemePlusHost);
    461                         if (up != null && up[0] != null) {
    462                             setUsernamePassword(up[0], up[1]);
    463                         }
    464                     }
    465                 }
    466                 WebViewWorker.getHandler().sendEmptyMessage(
    467                         WebViewWorker.MSG_TRIM_CACHE);
    468                 break;
    469             }
    470 
    471             case POLICY_FUNCTION: {
    472                 nativeCallPolicyFunction(msg.arg1, msg.arg2);
    473                 break;
    474             }
    475 
    476             case ORIENTATION_CHANGED: {
    477                 if (mOrientation != msg.arg1) {
    478                     mOrientation = msg.arg1;
    479                     nativeOrientationChanged(msg.arg1);
    480                 }
    481                 break;
    482             }
    483 
    484             default:
    485                 break;
    486         }
    487     }
    488 
    489     /**
    490      * Punch-through for WebCore to set the document
    491      * title. Inform the Activity of the new title.
    492      * @param title The new title of the document.
    493      */
    494     private void setTitle(String title) {
    495         // FIXME: The activity must call getTitle (a native method) to get the
    496         // title. We should try and cache the title if we can also keep it in
    497         // sync with the document.
    498         mCallbackProxy.onReceivedTitle(title);
    499     }
    500 
    501     /**
    502      * Retrieves the render tree of this frame and puts it as the object for
    503      * the message and sends the message.
    504      * @param callback the message to use to send the render tree
    505      */
    506     public void externalRepresentation(Message callback) {
    507         callback.obj = externalRepresentation();;
    508         callback.sendToTarget();
    509     }
    510 
    511     /**
    512      * Return the render tree as a string
    513      */
    514     private native String externalRepresentation();
    515 
    516     /**
    517      * Retrieves the visual text of the current frame, puts it as the object for
    518      * the message and sends the message.
    519      * @param callback the message to use to send the visual text
    520      */
    521     public void documentAsText(Message callback) {
    522         callback.obj = documentAsText();;
    523         callback.sendToTarget();
    524     }
    525 
    526     /**
    527      * Return the text drawn on the screen as a string
    528      */
    529     private native String documentAsText();
    530 
    531     /*
    532      * This method is called by WebCore to inform the frame that
    533      * the Javascript window object has been cleared.
    534      * We should re-attach any attached js interfaces.
    535      */
    536     private void windowObjectCleared(int nativeFramePointer) {
    537         if (mJSInterfaceMap != null) {
    538             Iterator iter = mJSInterfaceMap.keySet().iterator();
    539             while (iter.hasNext())  {
    540                 String interfaceName = (String) iter.next();
    541                 nativeAddJavascriptInterface(nativeFramePointer,
    542                         mJSInterfaceMap.get(interfaceName), interfaceName);
    543             }
    544         }
    545     }
    546 
    547     /**
    548      * This method is called by WebCore to check whether application
    549      * wants to hijack url loading
    550      */
    551     public boolean handleUrl(String url) {
    552         if (mLoadInitFromJava == true) {
    553             return false;
    554         }
    555         if (mCallbackProxy.shouldOverrideUrlLoading(url)) {
    556             // if the url is hijacked, reset the state of the BrowserFrame
    557             didFirstLayout();
    558             return true;
    559         } else {
    560             return false;
    561         }
    562     }
    563 
    564     public void addJavascriptInterface(Object obj, String interfaceName) {
    565         if (mJSInterfaceMap == null) {
    566             mJSInterfaceMap = new HashMap<String, Object>();
    567         }
    568         if (mJSInterfaceMap.containsKey(interfaceName)) {
    569             mJSInterfaceMap.remove(interfaceName);
    570         }
    571         mJSInterfaceMap.put(interfaceName, obj);
    572     }
    573 
    574     /**
    575      * Called by JNI.  Given a URI, find the associated file and return its size
    576      * @param uri A String representing the URI of the desired file.
    577      * @return int The size of the given file.
    578      */
    579     private int getFileSize(String uri) {
    580         int size = 0;
    581         try {
    582             InputStream stream = mContext.getContentResolver()
    583                             .openInputStream(Uri.parse(uri));
    584             size = stream.available();
    585             stream.close();
    586         } catch (Exception e) {}
    587         return size;
    588     }
    589 
    590     /**
    591      * Called by JNI.  Given a URI, a buffer, and an offset into the buffer,
    592      * copy the resource into buffer.
    593      * @param uri A String representing the URI of the desired file.
    594      * @param buffer The byte array to copy the data into.
    595      * @param offset The offet into buffer to place the data.
    596      * @param expectedSize The size that the buffer has allocated for this file.
    597      * @return int The size of the given file, or zero if it fails.
    598      */
    599     private int getFile(String uri, byte[] buffer, int offset,
    600             int expectedSize) {
    601         int size = 0;
    602         try {
    603             InputStream stream = mContext.getContentResolver()
    604                             .openInputStream(Uri.parse(uri));
    605             size = stream.available();
    606             if (size <= expectedSize && buffer != null
    607                     && buffer.length - offset >= size) {
    608                 stream.read(buffer, offset, size);
    609             } else {
    610                 size = 0;
    611             }
    612             stream.close();
    613         } catch (java.io.FileNotFoundException e) {
    614             Log.e(LOGTAG, "FileNotFoundException:" + e);
    615             size = 0;
    616         } catch (java.io.IOException e2) {
    617             Log.e(LOGTAG, "IOException: " + e2);
    618             size = 0;
    619         }
    620         return size;
    621     }
    622 
    623     /**
    624      * Start loading a resource.
    625      * @param loaderHandle The native ResourceLoader that is the target of the
    626      *                     data.
    627      * @param url The url to load.
    628      * @param method The http method.
    629      * @param headers The http headers.
    630      * @param postData If the method is "POST" postData is sent as the request
    631      *                 body. Is null when empty.
    632      * @param postDataIdentifier If the post data contained form this is the form identifier, otherwise it is 0.
    633      * @param cacheMode The cache mode to use when loading this resource. See WebSettings.setCacheMode
    634      * @param mainResource True if the this resource is the main request, not a supporting resource
    635      * @param userGesture
    636      * @param synchronous True if the load is synchronous.
    637      * @return A newly created LoadListener object.
    638      */
    639     private LoadListener startLoadingResource(int loaderHandle,
    640                                               String url,
    641                                               String method,
    642                                               HashMap headers,
    643                                               byte[] postData,
    644                                               long postDataIdentifier,
    645                                               int cacheMode,
    646                                               boolean mainResource,
    647                                               boolean userGesture,
    648                                               boolean synchronous,
    649                                               String username,
    650                                               String password) {
    651         PerfChecker checker = new PerfChecker();
    652 
    653         if (mSettings.getCacheMode() != WebSettings.LOAD_DEFAULT) {
    654             cacheMode = mSettings.getCacheMode();
    655         }
    656 
    657         if (method.equals("POST")) {
    658             // Don't use the cache on POSTs when issuing a normal POST
    659             // request.
    660             if (cacheMode == WebSettings.LOAD_NORMAL) {
    661                 cacheMode = WebSettings.LOAD_NO_CACHE;
    662             }
    663             if (mSettings.getSavePassword() && hasPasswordField()) {
    664                 try {
    665                     if (DebugFlags.BROWSER_FRAME) {
    666                         Assert.assertNotNull(mCallbackProxy.getBackForwardList()
    667                                 .getCurrentItem());
    668                     }
    669                     WebAddress uri = new WebAddress(mCallbackProxy
    670                             .getBackForwardList().getCurrentItem().getUrl());
    671                     String schemePlusHost = uri.mScheme + uri.mHost;
    672                     String[] ret = getUsernamePassword();
    673                     // Has the user entered a username/password pair and is
    674                     // there some POST data
    675                     if (ret != null && postData != null &&
    676                             ret[0].length() > 0 && ret[1].length() > 0) {
    677                         // Check to see if the username & password appear in
    678                         // the post data (there could be another form on the
    679                         // page and that was posted instead.
    680                         String postString = new String(postData);
    681                         if (postString.contains(URLEncoder.encode(ret[0])) &&
    682                                 postString.contains(URLEncoder.encode(ret[1]))) {
    683                             String[] saved = mDatabase.getUsernamePassword(
    684                                     schemePlusHost);
    685                             if (saved != null) {
    686                                 // null username implies that user has chosen not to
    687                                 // save password
    688                                 if (saved[0] != null) {
    689                                     // non-null username implies that user has
    690                                     // chosen to save password, so update the
    691                                     // recorded password
    692                                     mDatabase.setUsernamePassword(
    693                                             schemePlusHost, ret[0], ret[1]);
    694                                 }
    695                             } else {
    696                                 // CallbackProxy will handle creating the resume
    697                                 // message
    698                                 mCallbackProxy.onSavePassword(schemePlusHost, ret[0],
    699                                         ret[1], null);
    700                             }
    701                         }
    702                     }
    703                 } catch (ParseException ex) {
    704                     // if it is bad uri, don't save its password
    705                 }
    706 
    707             }
    708         }
    709 
    710         // is this resource the main-frame top-level page?
    711         boolean isMainFramePage = mIsMainFrame;
    712 
    713         if (DebugFlags.BROWSER_FRAME) {
    714             Log.v(LOGTAG, "startLoadingResource: url=" + url + ", method="
    715                     + method + ", postData=" + postData + ", isMainFramePage="
    716                     + isMainFramePage + ", mainResource=" + mainResource
    717                     + ", userGesture=" + userGesture);
    718         }
    719 
    720         // Create a LoadListener
    721         LoadListener loadListener = LoadListener.getLoadListener(mContext,
    722                 this, url, loaderHandle, synchronous, isMainFramePage,
    723                 mainResource, userGesture, postDataIdentifier, username, password);
    724 
    725         mCallbackProxy.onLoadResource(url);
    726 
    727         if (LoadListener.getNativeLoaderCount() > MAX_OUTSTANDING_REQUESTS) {
    728             // send an error message, so that loadListener can be deleted
    729             // after this is returned. This is important as LoadListener's
    730             // nativeError will remove the request from its DocLoader's request
    731             // list. But the set up is not done until this method is returned.
    732             loadListener.error(
    733                     android.net.http.EventHandler.ERROR, mContext.getString(
    734                             com.android.internal.R.string.httpErrorTooManyRequests));
    735             return loadListener;
    736         }
    737 
    738         FrameLoader loader = new FrameLoader(loadListener, mSettings, method);
    739         loader.setHeaders(headers);
    740         loader.setPostData(postData);
    741         // Set the load mode to the mode used for the current page.
    742         // If WebKit wants validation, go to network directly.
    743         loader.setCacheMode(headers.containsKey("If-Modified-Since")
    744                 || headers.containsKey("If-None-Match") ?
    745                         WebSettings.LOAD_NO_CACHE : cacheMode);
    746         // Set referrer to current URL?
    747         if (!loader.executeLoad()) {
    748             checker.responseAlert("startLoadingResource fail");
    749         }
    750         checker.responseAlert("startLoadingResource succeed");
    751 
    752         return !synchronous ? loadListener : null;
    753     }
    754 
    755     /**
    756      * Set the progress for the browser activity.  Called by native code.
    757      * Uses a delay so it does not happen too often.
    758      * @param newProgress An int between zero and one hundred representing
    759      *                    the current progress percentage of loading the page.
    760      */
    761     private void setProgress(int newProgress) {
    762         mCallbackProxy.onProgressChanged(newProgress);
    763         if (newProgress == 100) {
    764             sendMessageDelayed(obtainMessage(FRAME_COMPLETED), 100);
    765         }
    766         // FIXME: Need to figure out a better way to switch out of the history
    767         // drawing mode. Maybe we can somehow compare the history picture with
    768         // the current picture, and switch when it contains more content.
    769         if (mFirstLayoutDone && newProgress > TRANSITION_SWITCH_THRESHOLD) {
    770             mCallbackProxy.switchOutDrawHistory();
    771         }
    772     }
    773 
    774     /**
    775      * Send the icon to the activity for display.
    776      * @param icon A Bitmap representing a page's favicon.
    777      */
    778     private void didReceiveIcon(Bitmap icon) {
    779         mCallbackProxy.onReceivedIcon(icon);
    780     }
    781 
    782     // Called by JNI when an apple-touch-icon attribute was found.
    783     private void didReceiveTouchIconUrl(String url, boolean precomposed) {
    784         mCallbackProxy.onReceivedTouchIconUrl(url, precomposed);
    785     }
    786 
    787     /**
    788      * Request a new window from the client.
    789      * @return The BrowserFrame object stored in the new WebView.
    790      */
    791     private BrowserFrame createWindow(boolean dialog, boolean userGesture) {
    792         return mCallbackProxy.createWindow(dialog, userGesture);
    793     }
    794 
    795     /**
    796      * Try to focus this WebView.
    797      */
    798     private void requestFocus() {
    799         mCallbackProxy.onRequestFocus();
    800     }
    801 
    802     /**
    803      * Close this frame and window.
    804      */
    805     private void closeWindow(WebViewCore w) {
    806         mCallbackProxy.onCloseWindow(w.getWebView());
    807     }
    808 
    809     // XXX: Must match PolicyAction in FrameLoaderTypes.h in webcore
    810     static final int POLICY_USE = 0;
    811     static final int POLICY_IGNORE = 2;
    812 
    813     private void decidePolicyForFormResubmission(int policyFunction) {
    814         Message dontResend = obtainMessage(POLICY_FUNCTION, policyFunction,
    815                 POLICY_IGNORE);
    816         Message resend = obtainMessage(POLICY_FUNCTION, policyFunction,
    817                 POLICY_USE);
    818         mCallbackProxy.onFormResubmission(dontResend, resend);
    819     }
    820 
    821     /**
    822      * Tell the activity to update its global history.
    823      */
    824     private void updateVisitedHistory(String url, boolean isReload) {
    825         mCallbackProxy.doUpdateVisitedHistory(url, isReload);
    826     }
    827 
    828     /**
    829      * Get the CallbackProxy for sending messages to the UI thread.
    830      */
    831     /* package */ CallbackProxy getCallbackProxy() {
    832         return mCallbackProxy;
    833     }
    834 
    835     /**
    836      * Returns the User Agent used by this frame
    837      */
    838     String getUserAgentString() {
    839         return mSettings.getUserAgentString();
    840     }
    841 
    842     // These ids need to be in sync with enum rawResId in PlatformBridge.h
    843     private static final int NODOMAIN = 1;
    844     private static final int LOADERROR = 2;
    845     private static final int DRAWABLEDIR = 3;
    846     private static final int FILE_UPLOAD_LABEL = 4;
    847     private static final int RESET_LABEL = 5;
    848     private static final int SUBMIT_LABEL = 6;
    849 
    850     String getRawResFilename(int id) {
    851         int resid;
    852         switch (id) {
    853             case NODOMAIN:
    854                 resid = com.android.internal.R.raw.nodomain;
    855                 break;
    856 
    857             case LOADERROR:
    858                 resid = com.android.internal.R.raw.loaderror;
    859                 break;
    860 
    861             case DRAWABLEDIR:
    862                 // use one known resource to find the drawable directory
    863                 resid = com.android.internal.R.drawable.btn_check_off;
    864                 break;
    865 
    866             case FILE_UPLOAD_LABEL:
    867                 return mContext.getResources().getString(
    868                         com.android.internal.R.string.upload_file);
    869 
    870             case RESET_LABEL:
    871                 return mContext.getResources().getString(
    872                         com.android.internal.R.string.reset);
    873 
    874             case SUBMIT_LABEL:
    875                 return mContext.getResources().getString(
    876                         com.android.internal.R.string.submit);
    877 
    878             default:
    879                 Log.e(LOGTAG, "getRawResFilename got incompatible resource ID");
    880                 return "";
    881         }
    882         TypedValue value = new TypedValue();
    883         mContext.getResources().getValue(resid, value, true);
    884         if (id == DRAWABLEDIR) {
    885             String path = value.string.toString();
    886             int index = path.lastIndexOf('/');
    887             if (index < 0) {
    888                 Log.e(LOGTAG, "Can't find drawable directory.");
    889                 return "";
    890             }
    891             return path.substring(0, index + 1);
    892         }
    893         return value.string.toString();
    894     }
    895 
    896     private float density() {
    897         return mContext.getResources().getDisplayMetrics().density;
    898     }
    899 
    900     //==========================================================================
    901     // native functions
    902     //==========================================================================
    903 
    904     /**
    905      * Create a new native frame for a given WebView
    906      * @param w     A WebView that the frame draws into.
    907      * @param am    AssetManager to use to get assets.
    908      * @param list  The native side will add and remove items from this list as
    909      *              the native list changes.
    910      */
    911     private native void nativeCreateFrame(WebViewCore w, AssetManager am,
    912             WebBackForwardList list);
    913 
    914     /**
    915      * Destroy the native frame.
    916      */
    917     public native void nativeDestroyFrame();
    918 
    919     private native void nativeCallPolicyFunction(int policyFunction,
    920             int decision);
    921 
    922     /**
    923      * Reload the current main frame.
    924      */
    925     public native void reload(boolean allowStale);
    926 
    927     /**
    928      * Go back or forward the number of steps given.
    929      * @param steps A negative or positive number indicating the direction
    930      *              and number of steps to move.
    931      */
    932     private native void nativeGoBackOrForward(int steps);
    933 
    934     /**
    935      * stringByEvaluatingJavaScriptFromString will execute the
    936      * JS passed in in the context of this browser frame.
    937      * @param script A javascript string to execute
    938      *
    939      * @return string result of execution or null
    940      */
    941     public native String stringByEvaluatingJavaScriptFromString(String script);
    942 
    943     /**
    944      * Add a javascript interface to the main frame.
    945      */
    946     private native void nativeAddJavascriptInterface(int nativeFramePointer,
    947             Object obj, String interfaceName);
    948 
    949     /**
    950      * Enable or disable the native cache.
    951      */
    952     /* FIXME: The native cache is always on for now until we have a better
    953      * solution for our 2 caches. */
    954     private native void setCacheDisabled(boolean disabled);
    955 
    956     public native boolean cacheDisabled();
    957 
    958     public native void clearCache();
    959 
    960     /**
    961      * Returns false if the url is bad.
    962      */
    963     private native void nativeLoadUrl(String url, Map<String, String> headers);
    964 
    965     private native void nativePostUrl(String url, byte[] postData);
    966 
    967     private native void nativeLoadData(String baseUrl, String data,
    968             String mimeType, String encoding, String historyUrl);
    969 
    970     /**
    971      * Stop loading the current page.
    972      */
    973     public void stopLoading() {
    974         if (mIsMainFrame) {
    975             resetLoadingStates();
    976         }
    977         nativeStopLoading();
    978     }
    979 
    980     private native void nativeStopLoading();
    981 
    982     /**
    983      * Return true if the document has images.
    984      */
    985     public native boolean documentHasImages();
    986 
    987     /**
    988      * @return TRUE if there is a password field in the current frame
    989      */
    990     private native boolean hasPasswordField();
    991 
    992     /**
    993      * Get username and password in the current frame. If found, String[0] is
    994      * username and String[1] is password. Otherwise return NULL.
    995      * @return String[]
    996      */
    997     private native String[] getUsernamePassword();
    998 
    999     /**
   1000      * Set username and password to the proper fields in the current frame
   1001      * @param username
   1002      * @param password
   1003      */
   1004     private native void setUsernamePassword(String username, String password);
   1005 
   1006     /**
   1007      * Get form's "text" type data associated with the current frame.
   1008      * @return HashMap If succeed, returns a list of name/value pair. Otherwise
   1009      *         returns null.
   1010      */
   1011     private native HashMap getFormTextData();
   1012 
   1013     private native void nativeOrientationChanged(int orientation);
   1014 }
   1015