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 
     75     // Is this frame the main frame?
     76     private boolean mIsMainFrame;
     77 
     78     // Attached Javascript interfaces
     79     private Map<String, Object> mJSInterfaceMap;
     80 
     81     // message ids
     82     // a message posted when a frame loading is completed
     83     static final int FRAME_COMPLETED = 1001;
     84     // orientation change message
     85     static final int ORIENTATION_CHANGED = 1002;
     86     // a message posted when the user decides the policy
     87     static final int POLICY_FUNCTION = 1003;
     88 
     89     // Note: need to keep these in sync with FrameLoaderTypes.h in native
     90     static final int FRAME_LOADTYPE_STANDARD = 0;
     91     static final int FRAME_LOADTYPE_BACK = 1;
     92     static final int FRAME_LOADTYPE_FORWARD = 2;
     93     static final int FRAME_LOADTYPE_INDEXEDBACKFORWARD = 3;
     94     static final int FRAME_LOADTYPE_RELOAD = 4;
     95     static final int FRAME_LOADTYPE_RELOADALLOWINGSTALEDATA = 5;
     96     static final int FRAME_LOADTYPE_SAME = 6;
     97     static final int FRAME_LOADTYPE_REDIRECT = 7;
     98     static final int FRAME_LOADTYPE_REPLACE = 8;
     99 
    100     // A progress threshold to switch from history Picture to live Picture
    101     private static final int TRANSITION_SWITCH_THRESHOLD = 75;
    102 
    103     // This is a field accessed by native code as well as package classes.
    104     /*package*/ int mNativeFrame;
    105 
    106     // Static instance of a JWebCoreJavaBridge to handle timer and cookie
    107     // requests from WebCore.
    108     static JWebCoreJavaBridge sJavaBridge;
    109 
    110     private static class ConfigCallback implements ComponentCallbacks {
    111         private final ArrayList<WeakReference<Handler>> mHandlers =
    112                 new ArrayList<WeakReference<Handler>>();
    113         private final WindowManager mWindowManager;
    114 
    115         ConfigCallback(WindowManager wm) {
    116             mWindowManager = wm;
    117         }
    118 
    119         public synchronized void addHandler(Handler h) {
    120             // No need to ever remove a Handler. If the BrowserFrame is
    121             // destroyed, it will be collected and the WeakReference set to
    122             // null. If it happens to still be around during a configuration
    123             // change, the message will be ignored.
    124             mHandlers.add(new WeakReference<Handler>(h));
    125         }
    126 
    127         public void onConfigurationChanged(Configuration newConfig) {
    128             if (mHandlers.size() == 0) {
    129                 return;
    130             }
    131             int orientation =
    132                     mWindowManager.getDefaultDisplay().getOrientation();
    133             switch (orientation) {
    134                 case Surface.ROTATION_90:
    135                     orientation = 90;
    136                     break;
    137                 case Surface.ROTATION_180:
    138                     orientation = 180;
    139                     break;
    140                 case Surface.ROTATION_270:
    141                     orientation = -90;
    142                     break;
    143                 case Surface.ROTATION_0:
    144                     orientation = 0;
    145                     break;
    146                 default:
    147                     break;
    148             }
    149             synchronized (this) {
    150                 // Create a list of handlers to remove. Go ahead and make it
    151                 // the same size to avoid resizing.
    152                 ArrayList<WeakReference> handlersToRemove =
    153                         new ArrayList<WeakReference>(mHandlers.size());
    154                 for (WeakReference<Handler> wh : mHandlers) {
    155                     Handler h = wh.get();
    156                     if (h != null) {
    157                         h.sendMessage(h.obtainMessage(ORIENTATION_CHANGED,
    158                                     orientation, 0));
    159                     } else {
    160                         handlersToRemove.add(wh);
    161                     }
    162                 }
    163                 // Now remove all the null references.
    164                 for (WeakReference weak : handlersToRemove) {
    165                     mHandlers.remove(weak);
    166                 }
    167             }
    168         }
    169 
    170         public void onLowMemory() {}
    171     }
    172     static ConfigCallback sConfigCallback;
    173 
    174     /**
    175      * Create a new BrowserFrame to be used in an application.
    176      * @param context An application context to use when retrieving assets.
    177      * @param w A WebViewCore used as the view for this frame.
    178      * @param proxy A CallbackProxy for posting messages to the UI thread and
    179      *              querying a client for information.
    180      * @param settings A WebSettings object that holds all settings.
    181      * XXX: Called by WebCore thread.
    182      */
    183     public BrowserFrame(Context context, WebViewCore w, CallbackProxy proxy,
    184             WebSettings settings, Map<String, Object> javascriptInterfaces) {
    185 
    186         Context appContext = context.getApplicationContext();
    187 
    188         // Create a global JWebCoreJavaBridge to handle timers and
    189         // cookies in the WebCore thread.
    190         if (sJavaBridge == null) {
    191             sJavaBridge = new JWebCoreJavaBridge();
    192             // set WebCore native cache size
    193             ActivityManager am = (ActivityManager) context
    194                     .getSystemService(Context.ACTIVITY_SERVICE);
    195             if (am.getMemoryClass() > 16) {
    196                 sJavaBridge.setCacheSize(8 * 1024 * 1024);
    197             } else {
    198                 sJavaBridge.setCacheSize(4 * 1024 * 1024);
    199             }
    200             // initialize CacheManager
    201             CacheManager.init(appContext);
    202             // create CookieSyncManager with current Context
    203             CookieSyncManager.createInstance(appContext);
    204             // create PluginManager with current Context
    205             PluginManager.getInstance(appContext);
    206         }
    207 
    208         if (sConfigCallback == null) {
    209             sConfigCallback = new ConfigCallback(
    210                     (WindowManager) context.getSystemService(
    211                             Context.WINDOW_SERVICE));
    212             ViewRoot.addConfigCallback(sConfigCallback);
    213         }
    214         sConfigCallback.addHandler(this);
    215 
    216         mJSInterfaceMap = javascriptInterfaces;
    217 
    218         mSettings = settings;
    219         mContext = context;
    220         mCallbackProxy = proxy;
    221         mDatabase = WebViewDatabase.getInstance(appContext);
    222         mWebViewCore = w;
    223 
    224         AssetManager am = context.getAssets();
    225         nativeCreateFrame(w, am, proxy.getBackForwardList());
    226 
    227         if (DebugFlags.BROWSER_FRAME) {
    228             Log.v(LOGTAG, "BrowserFrame constructor: this=" + this);
    229         }
    230     }
    231 
    232     /**
    233      * Load a url from the network or the filesystem into the main frame.
    234      * Following the same behaviour as Safari, javascript: URLs are not passed
    235      * to the main frame, instead they are evaluated immediately.
    236      * @param url The url to load.
    237      * @param extraHeaders The extra headers sent with this url. This should not
    238      *            include the common headers like "user-agent". If it does, it
    239      *            will be replaced by the intrinsic value of the WebView.
    240      */
    241     public void loadUrl(String url, Map<String, String> extraHeaders) {
    242         mLoadInitFromJava = true;
    243         if (URLUtil.isJavaScriptUrl(url)) {
    244             // strip off the scheme and evaluate the string
    245             stringByEvaluatingJavaScriptFromString(
    246                     url.substring("javascript:".length()));
    247         } else {
    248             nativeLoadUrl(url, extraHeaders);
    249         }
    250         mLoadInitFromJava = false;
    251     }
    252 
    253     /**
    254      * Load a url with "POST" method from the network into the main frame.
    255      * @param url The url to load.
    256      * @param data The data for POST request.
    257      */
    258     public void postUrl(String url, byte[] data) {
    259         mLoadInitFromJava = true;
    260         nativePostUrl(url, data);
    261         mLoadInitFromJava = false;
    262     }
    263 
    264     /**
    265      * Load the content as if it was loaded by the provided base URL. The
    266      * historyUrl is used as the history entry for the load data.
    267      *
    268      * @param baseUrl Base URL used to resolve relative paths in the content
    269      * @param data Content to render in the browser
    270      * @param mimeType Mimetype of the data being passed in
    271      * @param encoding Character set encoding of the provided data.
    272      * @param historyUrl URL to use as the history entry.
    273      */
    274     public void loadData(String baseUrl, String data, String mimeType,
    275             String encoding, String historyUrl) {
    276         mLoadInitFromJava = true;
    277         if (historyUrl == null || historyUrl.length() == 0) {
    278             historyUrl = "about:blank";
    279         }
    280         if (data == null) {
    281             data = "";
    282         }
    283 
    284         // Setup defaults for missing values. These defaults where taken from
    285         // WebKit's WebFrame.mm
    286         if (baseUrl == null || baseUrl.length() == 0) {
    287             baseUrl = "about:blank";
    288         }
    289         if (mimeType == null || mimeType.length() == 0) {
    290             mimeType = "text/html";
    291         }
    292         nativeLoadData(baseUrl, data, mimeType, encoding, historyUrl);
    293         mLoadInitFromJava = false;
    294     }
    295 
    296     /**
    297      * Go back or forward the number of steps given.
    298      * @param steps A negative or positive number indicating the direction
    299      *              and number of steps to move.
    300      */
    301     public void goBackOrForward(int steps) {
    302         mLoadInitFromJava = true;
    303         nativeGoBackOrForward(steps);
    304         mLoadInitFromJava = false;
    305     }
    306 
    307     /**
    308      * native callback
    309      * Report an error to an activity.
    310      * @param errorCode The HTTP error code.
    311      * @param description A String description.
    312      * TODO: Report all errors including resource errors but include some kind
    313      * of domain identifier. Change errorCode to an enum for a cleaner
    314      * interface.
    315      */
    316     private void reportError(final int errorCode, final String description,
    317             final String failingUrl) {
    318         // As this is called for the main resource and loading will be stopped
    319         // after, reset the state variables.
    320         resetLoadingStates();
    321         mCallbackProxy.onReceivedError(errorCode, description, failingUrl);
    322     }
    323 
    324     private void resetLoadingStates() {
    325         mCommitted = true;
    326         mFirstLayoutDone = true;
    327     }
    328 
    329     /* package */boolean committed() {
    330         return mCommitted;
    331     }
    332 
    333     /* package */boolean firstLayoutDone() {
    334         return mFirstLayoutDone;
    335     }
    336 
    337     /* package */int loadType() {
    338         return mLoadType;
    339     }
    340 
    341     /* package */void didFirstLayout() {
    342         if (!mFirstLayoutDone) {
    343             mFirstLayoutDone = true;
    344             // ensure {@link WebViewCore#webkitDraw} is called as we were
    345             // blocking the update in {@link #loadStarted}
    346             mWebViewCore.contentDraw();
    347         }
    348     }
    349 
    350     /**
    351      * native callback
    352      * Indicates the beginning of a new load.
    353      * This method will be called once for the main frame.
    354      */
    355     private void loadStarted(String url, Bitmap favicon, int loadType,
    356             boolean isMainFrame) {
    357         mIsMainFrame = isMainFrame;
    358 
    359         if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) {
    360             mLoadType = loadType;
    361 
    362             if (isMainFrame) {
    363                 // Call onPageStarted for main frames.
    364                 mCallbackProxy.onPageStarted(url, favicon);
    365                 // as didFirstLayout() is only called for the main frame, reset
    366                 // mFirstLayoutDone only for the main frames
    367                 mFirstLayoutDone = false;
    368                 mCommitted = false;
    369                 // remove pending draw to block update until mFirstLayoutDone is
    370                 // set to true in didFirstLayout()
    371                 mWebViewCore.removeMessages(WebViewCore.EventHub.WEBKIT_DRAW);
    372             }
    373 
    374             // Note: only saves committed form data in standard load
    375             if (loadType == FRAME_LOADTYPE_STANDARD
    376                     && mSettings.getSaveFormData()) {
    377                 final WebHistoryItem h = mCallbackProxy.getBackForwardList()
    378                         .getCurrentItem();
    379                 if (h != null) {
    380                     String currentUrl = h.getUrl();
    381                     if (currentUrl != null) {
    382                         mDatabase.setFormData(currentUrl, getFormTextData());
    383                     }
    384                 }
    385             }
    386         }
    387     }
    388 
    389     /**
    390      * native callback
    391      * Indicates the WebKit has committed to the new load
    392      */
    393     private void transitionToCommitted(int loadType, boolean isMainFrame) {
    394         // loadType is not used yet
    395         if (isMainFrame) {
    396             mCommitted = true;
    397             mWebViewCore.getWebView().mViewManager.postResetStateAll();
    398         }
    399     }
    400 
    401     /**
    402      * native callback
    403      * <p>
    404      * Indicates the end of a new load.
    405      * This method will be called once for the main frame.
    406      */
    407     private void loadFinished(String url, int loadType, boolean isMainFrame) {
    408         // mIsMainFrame and isMainFrame are better be equal!!!
    409 
    410         if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) {
    411             if (isMainFrame) {
    412                 resetLoadingStates();
    413                 mCallbackProxy.switchOutDrawHistory();
    414                 mCallbackProxy.onPageFinished(url);
    415             }
    416         }
    417     }
    418 
    419     /**
    420      * We have received an SSL certificate for the main top-level page.
    421      *
    422      * !!!Called from the network thread!!!
    423      */
    424     void certificate(SslCertificate certificate) {
    425         if (mIsMainFrame) {
    426             // we want to make this call even if the certificate is null
    427             // (ie, the site is not secure)
    428             mCallbackProxy.onReceivedCertificate(certificate);
    429         }
    430     }
    431 
    432     /**
    433      * Destroy all native components of the BrowserFrame.
    434      */
    435     public void destroy() {
    436         nativeDestroyFrame();
    437         mBlockMessages = true;
    438         removeCallbacksAndMessages(null);
    439     }
    440 
    441     /**
    442      * Handle messages posted to us.
    443      * @param msg The message to handle.
    444      */
    445     @Override
    446     public void handleMessage(Message msg) {
    447         if (mBlockMessages) {
    448             return;
    449         }
    450         switch (msg.what) {
    451             case FRAME_COMPLETED: {
    452                 if (mSettings.getSavePassword() && hasPasswordField()) {
    453                     WebHistoryItem item = mCallbackProxy.getBackForwardList()
    454                             .getCurrentItem();
    455                     if (item != null) {
    456                         WebAddress uri = new WebAddress(item.getUrl());
    457                         String schemePlusHost = uri.mScheme + uri.mHost;
    458                         String[] up =
    459                                 mDatabase.getUsernamePassword(schemePlusHost);
    460                         if (up != null && up[0] != null) {
    461                             setUsernamePassword(up[0], up[1]);
    462                         }
    463                     }
    464                 }
    465                 WebViewWorker.getHandler().sendEmptyMessage(
    466                         WebViewWorker.MSG_TRIM_CACHE);
    467                 break;
    468             }
    469 
    470             case POLICY_FUNCTION: {
    471                 nativeCallPolicyFunction(msg.arg1, msg.arg2);
    472                 break;
    473             }
    474 
    475             case ORIENTATION_CHANGED: {
    476                 nativeOrientationChanged(msg.arg1);
    477                 break;
    478             }
    479 
    480             default:
    481                 break;
    482         }
    483     }
    484 
    485     /**
    486      * Punch-through for WebCore to set the document
    487      * title. Inform the Activity of the new title.
    488      * @param title The new title of the document.
    489      */
    490     private void setTitle(String title) {
    491         // FIXME: The activity must call getTitle (a native method) to get the
    492         // title. We should try and cache the title if we can also keep it in
    493         // sync with the document.
    494         mCallbackProxy.onReceivedTitle(title);
    495     }
    496 
    497     /**
    498      * Retrieves the render tree of this frame and puts it as the object for
    499      * the message and sends the message.
    500      * @param callback the message to use to send the render tree
    501      */
    502     public void externalRepresentation(Message callback) {
    503         callback.obj = externalRepresentation();;
    504         callback.sendToTarget();
    505     }
    506 
    507     /**
    508      * Return the render tree as a string
    509      */
    510     private native String externalRepresentation();
    511 
    512     /**
    513      * Retrieves the visual text of the current frame, puts it as the object for
    514      * the message and sends the message.
    515      * @param callback the message to use to send the visual text
    516      */
    517     public void documentAsText(Message callback) {
    518         callback.obj = documentAsText();;
    519         callback.sendToTarget();
    520     }
    521 
    522     /**
    523      * Return the text drawn on the screen as a string
    524      */
    525     private native String documentAsText();
    526 
    527     /*
    528      * This method is called by WebCore to inform the frame that
    529      * the Javascript window object has been cleared.
    530      * We should re-attach any attached js interfaces.
    531      */
    532     private void windowObjectCleared(int nativeFramePointer) {
    533         if (mJSInterfaceMap != null) {
    534             Iterator iter = mJSInterfaceMap.keySet().iterator();
    535             while (iter.hasNext())  {
    536                 String interfaceName = (String) iter.next();
    537                 nativeAddJavascriptInterface(nativeFramePointer,
    538                         mJSInterfaceMap.get(interfaceName), interfaceName);
    539             }
    540         }
    541     }
    542 
    543     /**
    544      * This method is called by WebCore to check whether application
    545      * wants to hijack url loading
    546      */
    547     public boolean handleUrl(String url) {
    548         if (mLoadInitFromJava == true) {
    549             return false;
    550         }
    551         if (mCallbackProxy.shouldOverrideUrlLoading(url)) {
    552             // if the url is hijacked, reset the state of the BrowserFrame
    553             didFirstLayout();
    554             return true;
    555         } else {
    556             return false;
    557         }
    558     }
    559 
    560     public void addJavascriptInterface(Object obj, String interfaceName) {
    561         if (mJSInterfaceMap == null) {
    562             mJSInterfaceMap = new HashMap<String, Object>();
    563         }
    564         if (mJSInterfaceMap.containsKey(interfaceName)) {
    565             mJSInterfaceMap.remove(interfaceName);
    566         }
    567         mJSInterfaceMap.put(interfaceName, obj);
    568     }
    569 
    570     /**
    571      * Called by JNI.  Given a URI, find the associated file and return its size
    572      * @param uri A String representing the URI of the desired file.
    573      * @return int The size of the given file.
    574      */
    575     private int getFileSize(String uri) {
    576         int size = 0;
    577         try {
    578             InputStream stream = mContext.getContentResolver()
    579                             .openInputStream(Uri.parse(uri));
    580             size = stream.available();
    581             stream.close();
    582         } catch (Exception e) {}
    583         return size;
    584     }
    585 
    586     /**
    587      * Called by JNI.  Given a URI, a buffer, and an offset into the buffer,
    588      * copy the resource into buffer.
    589      * @param uri A String representing the URI of the desired file.
    590      * @param buffer The byte array to copy the data into.
    591      * @param offset The offet into buffer to place the data.
    592      * @param expectedSize The size that the buffer has allocated for this file.
    593      * @return int The size of the given file, or zero if it fails.
    594      */
    595     private int getFile(String uri, byte[] buffer, int offset,
    596             int expectedSize) {
    597         int size = 0;
    598         try {
    599             InputStream stream = mContext.getContentResolver()
    600                             .openInputStream(Uri.parse(uri));
    601             size = stream.available();
    602             if (size <= expectedSize && buffer != null
    603                     && buffer.length - offset >= size) {
    604                 stream.read(buffer, offset, size);
    605             } else {
    606                 size = 0;
    607             }
    608             stream.close();
    609         } catch (java.io.FileNotFoundException e) {
    610             Log.e(LOGTAG, "FileNotFoundException:" + e);
    611             size = 0;
    612         } catch (java.io.IOException e2) {
    613             Log.e(LOGTAG, "IOException: " + e2);
    614             size = 0;
    615         }
    616         return size;
    617     }
    618 
    619     /**
    620      * Start loading a resource.
    621      * @param loaderHandle The native ResourceLoader that is the target of the
    622      *                     data.
    623      * @param url The url to load.
    624      * @param method The http method.
    625      * @param headers The http headers.
    626      * @param postData If the method is "POST" postData is sent as the request
    627      *                 body. Is null when empty.
    628      * @param postDataIdentifier If the post data contained form this is the form identifier, otherwise it is 0.
    629      * @param cacheMode The cache mode to use when loading this resource. See WebSettings.setCacheMode
    630      * @param mainResource True if the this resource is the main request, not a supporting resource
    631      * @param userGesture
    632      * @param synchronous True if the load is synchronous.
    633      * @return A newly created LoadListener object.
    634      */
    635     private LoadListener startLoadingResource(int loaderHandle,
    636                                               String url,
    637                                               String method,
    638                                               HashMap headers,
    639                                               byte[] postData,
    640                                               long postDataIdentifier,
    641                                               int cacheMode,
    642                                               boolean mainResource,
    643                                               boolean userGesture,
    644                                               boolean synchronous,
    645                                               String username,
    646                                               String password) {
    647         PerfChecker checker = new PerfChecker();
    648 
    649         if (mSettings.getCacheMode() != WebSettings.LOAD_DEFAULT) {
    650             cacheMode = mSettings.getCacheMode();
    651         }
    652 
    653         if (method.equals("POST")) {
    654             // Don't use the cache on POSTs when issuing a normal POST
    655             // request.
    656             if (cacheMode == WebSettings.LOAD_NORMAL) {
    657                 cacheMode = WebSettings.LOAD_NO_CACHE;
    658             }
    659             if (mSettings.getSavePassword() && hasPasswordField()) {
    660                 try {
    661                     if (DebugFlags.BROWSER_FRAME) {
    662                         Assert.assertNotNull(mCallbackProxy.getBackForwardList()
    663                                 .getCurrentItem());
    664                     }
    665                     WebAddress uri = new WebAddress(mCallbackProxy
    666                             .getBackForwardList().getCurrentItem().getUrl());
    667                     String schemePlusHost = uri.mScheme + uri.mHost;
    668                     String[] ret = getUsernamePassword();
    669                     // Has the user entered a username/password pair and is
    670                     // there some POST data
    671                     if (ret != null && postData != null &&
    672                             ret[0].length() > 0 && ret[1].length() > 0) {
    673                         // Check to see if the username & password appear in
    674                         // the post data (there could be another form on the
    675                         // page and that was posted instead.
    676                         String postString = new String(postData);
    677                         if (postString.contains(URLEncoder.encode(ret[0])) &&
    678                                 postString.contains(URLEncoder.encode(ret[1]))) {
    679                             String[] saved = mDatabase.getUsernamePassword(
    680                                     schemePlusHost);
    681                             if (saved != null) {
    682                                 // null username implies that user has chosen not to
    683                                 // save password
    684                                 if (saved[0] != null) {
    685                                     // non-null username implies that user has
    686                                     // chosen to save password, so update the
    687                                     // recorded password
    688                                     mDatabase.setUsernamePassword(
    689                                             schemePlusHost, ret[0], ret[1]);
    690                                 }
    691                             } else {
    692                                 // CallbackProxy will handle creating the resume
    693                                 // message
    694                                 mCallbackProxy.onSavePassword(schemePlusHost, ret[0],
    695                                         ret[1], null);
    696                             }
    697                         }
    698                     }
    699                 } catch (ParseException ex) {
    700                     // if it is bad uri, don't save its password
    701                 }
    702 
    703             }
    704         }
    705 
    706         // is this resource the main-frame top-level page?
    707         boolean isMainFramePage = mIsMainFrame;
    708 
    709         if (DebugFlags.BROWSER_FRAME) {
    710             Log.v(LOGTAG, "startLoadingResource: url=" + url + ", method="
    711                     + method + ", postData=" + postData + ", isMainFramePage="
    712                     + isMainFramePage + ", mainResource=" + mainResource
    713                     + ", userGesture=" + userGesture);
    714         }
    715 
    716         // Create a LoadListener
    717         LoadListener loadListener = LoadListener.getLoadListener(mContext,
    718                 this, url, loaderHandle, synchronous, isMainFramePage,
    719                 mainResource, userGesture, postDataIdentifier, username, password);
    720 
    721         mCallbackProxy.onLoadResource(url);
    722 
    723         if (LoadListener.getNativeLoaderCount() > MAX_OUTSTANDING_REQUESTS) {
    724             // send an error message, so that loadListener can be deleted
    725             // after this is returned. This is important as LoadListener's
    726             // nativeError will remove the request from its DocLoader's request
    727             // list. But the set up is not done until this method is returned.
    728             loadListener.error(
    729                     android.net.http.EventHandler.ERROR, mContext.getString(
    730                             com.android.internal.R.string.httpErrorTooManyRequests));
    731             return loadListener;
    732         }
    733 
    734         FrameLoader loader = new FrameLoader(loadListener, mSettings, method);
    735         loader.setHeaders(headers);
    736         loader.setPostData(postData);
    737         // Set the load mode to the mode used for the current page.
    738         // If WebKit wants validation, go to network directly.
    739         loader.setCacheMode(headers.containsKey("If-Modified-Since")
    740                 || headers.containsKey("If-None-Match") ?
    741                         WebSettings.LOAD_NO_CACHE : cacheMode);
    742         // Set referrer to current URL?
    743         if (!loader.executeLoad()) {
    744             checker.responseAlert("startLoadingResource fail");
    745         }
    746         checker.responseAlert("startLoadingResource succeed");
    747 
    748         return !synchronous ? loadListener : null;
    749     }
    750 
    751     /**
    752      * Set the progress for the browser activity.  Called by native code.
    753      * Uses a delay so it does not happen too often.
    754      * @param newProgress An int between zero and one hundred representing
    755      *                    the current progress percentage of loading the page.
    756      */
    757     private void setProgress(int newProgress) {
    758         mCallbackProxy.onProgressChanged(newProgress);
    759         if (newProgress == 100) {
    760             sendMessageDelayed(obtainMessage(FRAME_COMPLETED), 100);
    761         }
    762         // FIXME: Need to figure out a better way to switch out of the history
    763         // drawing mode. Maybe we can somehow compare the history picture with
    764         // the current picture, and switch when it contains more content.
    765         if (mFirstLayoutDone && newProgress > TRANSITION_SWITCH_THRESHOLD) {
    766             mCallbackProxy.switchOutDrawHistory();
    767         }
    768     }
    769 
    770     /**
    771      * Send the icon to the activity for display.
    772      * @param icon A Bitmap representing a page's favicon.
    773      */
    774     private void didReceiveIcon(Bitmap icon) {
    775         mCallbackProxy.onReceivedIcon(icon);
    776     }
    777 
    778     // Called by JNI when an apple-touch-icon attribute was found.
    779     private void didReceiveTouchIconUrl(String url, boolean precomposed) {
    780         mCallbackProxy.onReceivedTouchIconUrl(url, precomposed);
    781     }
    782 
    783     /**
    784      * Request a new window from the client.
    785      * @return The BrowserFrame object stored in the new WebView.
    786      */
    787     private BrowserFrame createWindow(boolean dialog, boolean userGesture) {
    788         WebView w = mCallbackProxy.createWindow(dialog, userGesture);
    789         if (w != null) {
    790             return w.getWebViewCore().getBrowserFrame();
    791         }
    792         return null;
    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