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.content.res.Resources;
     25 import android.content.res.Resources.NotFoundException;
     26 import android.graphics.Bitmap;
     27 import android.net.ParseException;
     28 import android.net.Uri;
     29 import android.net.WebAddress;
     30 import android.net.http.ErrorStrings;
     31 import android.net.http.SslCertificate;
     32 import android.net.http.SslError;
     33 import android.os.Handler;
     34 import android.os.Message;
     35 import android.util.Log;
     36 import android.util.TypedValue;
     37 import android.view.Surface;
     38 import android.view.ViewRootImpl;
     39 import android.view.WindowManager;
     40 
     41 import junit.framework.Assert;
     42 
     43 import java.io.IOException;
     44 import java.io.InputStream;
     45 import java.lang.ref.WeakReference;
     46 import java.net.URLEncoder;
     47 import java.nio.charset.Charsets;
     48 import java.security.PrivateKey;
     49 import java.security.cert.CertificateEncodingException;
     50 import java.security.cert.X509Certificate;
     51 import java.util.ArrayList;
     52 import java.util.HashMap;
     53 import java.util.HashSet;
     54 import java.util.Iterator;
     55 import java.util.Map;
     56 import java.util.Set;
     57 
     58 import org.apache.harmony.security.provider.cert.X509CertImpl;
     59 import org.apache.harmony.xnet.provider.jsse.OpenSSLKey;
     60 import org.apache.harmony.xnet.provider.jsse.OpenSSLKeyHolder;
     61 
     62 class BrowserFrame extends Handler {
     63 
     64     private static final String LOGTAG = "webkit";
     65 
     66     /**
     67      * Cap the number of LoadListeners that will be instantiated, so
     68      * we don't blow the GREF count.  Attempting to queue more than
     69      * this many requests will prompt an error() callback on the
     70      * request's LoadListener
     71      */
     72     private final static int MAX_OUTSTANDING_REQUESTS = 300;
     73 
     74     private final CallbackProxy mCallbackProxy;
     75     private final WebSettingsClassic mSettings;
     76     private final Context mContext;
     77     private final WebViewDatabaseClassic mDatabase;
     78     private final WebViewCore mWebViewCore;
     79     /* package */ boolean mLoadInitFromJava;
     80     private int mLoadType;
     81     private boolean mFirstLayoutDone = true;
     82     private boolean mCommitted = true;
     83     // Flag for blocking messages. This is used during destroy() so
     84     // that if the UI thread posts any messages after the message
     85     // queue has been cleared,they are ignored.
     86     private boolean mBlockMessages = false;
     87     private int mOrientation = -1;
     88 
     89     // Is this frame the main frame?
     90     private boolean mIsMainFrame;
     91 
     92     // Javascript interface object
     93     private class JSObject {
     94         Object object;
     95         boolean requireAnnotation;
     96 
     97         public JSObject(Object object, boolean requireAnnotation) {
     98             this.object = object;
     99             this.requireAnnotation = requireAnnotation;
    100         }
    101     }
    102 
    103     // Attached Javascript interfaces
    104     private Map<String, JSObject> mJavaScriptObjects;
    105     private Set<Object> mRemovedJavaScriptObjects;
    106 
    107     // Key store handler when Chromium HTTP stack is used.
    108     private KeyStoreHandler mKeyStoreHandler = null;
    109 
    110     // message ids
    111     // a message posted when a frame loading is completed
    112     static final int FRAME_COMPLETED = 1001;
    113     // orientation change message
    114     static final int ORIENTATION_CHANGED = 1002;
    115     // a message posted when the user decides the policy
    116     static final int POLICY_FUNCTION = 1003;
    117 
    118     // Note: need to keep these in sync with FrameLoaderTypes.h in native
    119     static final int FRAME_LOADTYPE_STANDARD = 0;
    120     static final int FRAME_LOADTYPE_BACK = 1;
    121     static final int FRAME_LOADTYPE_FORWARD = 2;
    122     static final int FRAME_LOADTYPE_INDEXEDBACKFORWARD = 3;
    123     static final int FRAME_LOADTYPE_RELOAD = 4;
    124     static final int FRAME_LOADTYPE_RELOADALLOWINGSTALEDATA = 5;
    125     static final int FRAME_LOADTYPE_SAME = 6;
    126     static final int FRAME_LOADTYPE_REDIRECT = 7;
    127     static final int FRAME_LOADTYPE_REPLACE = 8;
    128 
    129     // A progress threshold to switch from history Picture to live Picture
    130     private static final int TRANSITION_SWITCH_THRESHOLD = 75;
    131 
    132     // This is a field accessed by native code as well as package classes.
    133     /*package*/ int mNativeFrame;
    134 
    135     // Static instance of a JWebCoreJavaBridge to handle timer and cookie
    136     // requests from WebCore.
    137     static JWebCoreJavaBridge sJavaBridge;
    138 
    139     private static class ConfigCallback implements ComponentCallbacks {
    140         private final ArrayList<WeakReference<Handler>> mHandlers =
    141                 new ArrayList<WeakReference<Handler>>();
    142         private final WindowManager mWindowManager;
    143 
    144         ConfigCallback(WindowManager wm) {
    145             mWindowManager = wm;
    146         }
    147 
    148         public synchronized void addHandler(Handler h) {
    149             // No need to ever remove a Handler. If the BrowserFrame is
    150             // destroyed, it will be collected and the WeakReference set to
    151             // null. If it happens to still be around during a configuration
    152             // change, the message will be ignored.
    153             mHandlers.add(new WeakReference<Handler>(h));
    154         }
    155 
    156         public void onConfigurationChanged(Configuration newConfig) {
    157             if (mHandlers.size() == 0) {
    158                 return;
    159             }
    160             int orientation =
    161                     mWindowManager.getDefaultDisplay().getOrientation();
    162             switch (orientation) {
    163                 case Surface.ROTATION_90:
    164                     orientation = 90;
    165                     break;
    166                 case Surface.ROTATION_180:
    167                     orientation = 180;
    168                     break;
    169                 case Surface.ROTATION_270:
    170                     orientation = -90;
    171                     break;
    172                 case Surface.ROTATION_0:
    173                     orientation = 0;
    174                     break;
    175                 default:
    176                     break;
    177             }
    178             synchronized (this) {
    179                 // Create a list of handlers to remove. Go ahead and make it
    180                 // the same size to avoid resizing.
    181                 ArrayList<WeakReference> handlersToRemove =
    182                         new ArrayList<WeakReference>(mHandlers.size());
    183                 for (WeakReference<Handler> wh : mHandlers) {
    184                     Handler h = wh.get();
    185                     if (h != null) {
    186                         h.sendMessage(h.obtainMessage(ORIENTATION_CHANGED,
    187                                     orientation, 0));
    188                     } else {
    189                         handlersToRemove.add(wh);
    190                     }
    191                 }
    192                 // Now remove all the null references.
    193                 for (WeakReference weak : handlersToRemove) {
    194                     mHandlers.remove(weak);
    195                 }
    196             }
    197         }
    198 
    199         public void onLowMemory() {}
    200     }
    201     static ConfigCallback sConfigCallback;
    202 
    203     /**
    204      * Create a new BrowserFrame to be used in an application.
    205      * @param context An application context to use when retrieving assets.
    206      * @param w A WebViewCore used as the view for this frame.
    207      * @param proxy A CallbackProxy for posting messages to the UI thread and
    208      *              querying a client for information.
    209      * @param settings A WebSettings object that holds all settings.
    210      * XXX: Called by WebCore thread.
    211      */
    212     public BrowserFrame(Context context, WebViewCore w, CallbackProxy proxy,
    213             WebSettingsClassic settings, Map<String, Object> javascriptInterfaces) {
    214 
    215         Context appContext = context.getApplicationContext();
    216 
    217         // Create a global JWebCoreJavaBridge to handle timers and
    218         // cookies in the WebCore thread.
    219         if (sJavaBridge == null) {
    220             sJavaBridge = new JWebCoreJavaBridge();
    221             // set WebCore native cache size
    222             ActivityManager am = (ActivityManager) context
    223                     .getSystemService(Context.ACTIVITY_SERVICE);
    224             if (am.getMemoryClass() > 16) {
    225                 sJavaBridge.setCacheSize(8 * 1024 * 1024);
    226             } else {
    227                 sJavaBridge.setCacheSize(4 * 1024 * 1024);
    228             }
    229             // create CookieSyncManager with current Context
    230             CookieSyncManager.createInstance(appContext);
    231             // create PluginManager with current Context
    232             PluginManager.getInstance(appContext);
    233         }
    234 
    235         if (sConfigCallback == null) {
    236             sConfigCallback = new ConfigCallback(
    237                     (WindowManager) appContext.getSystemService(
    238                             Context.WINDOW_SERVICE));
    239             ViewRootImpl.addConfigCallback(sConfigCallback);
    240         }
    241         sConfigCallback.addHandler(this);
    242 
    243         mJavaScriptObjects = new HashMap<String, JSObject>();
    244         addJavaScriptObjects(javascriptInterfaces);
    245         mRemovedJavaScriptObjects = new HashSet<Object>();
    246 
    247         mSettings = settings;
    248         mContext = context;
    249         mCallbackProxy = proxy;
    250         mDatabase = WebViewDatabaseClassic.getInstance(appContext);
    251         mWebViewCore = w;
    252 
    253         AssetManager am = context.getAssets();
    254         nativeCreateFrame(w, am, proxy.getBackForwardList());
    255 
    256         if (DebugFlags.BROWSER_FRAME) {
    257             Log.v(LOGTAG, "BrowserFrame constructor: this=" + this);
    258         }
    259     }
    260 
    261     /**
    262      * Load a url from the network or the filesystem into the main frame.
    263      * Following the same behaviour as Safari, javascript: URLs are not passed
    264      * to the main frame, instead they are evaluated immediately.
    265      * @param url The url to load.
    266      * @param extraHeaders The extra headers sent with this url. This should not
    267      *            include the common headers like "user-agent". If it does, it
    268      *            will be replaced by the intrinsic value of the WebView.
    269      */
    270     public void loadUrl(String url, Map<String, String> extraHeaders) {
    271         mLoadInitFromJava = true;
    272         if (URLUtil.isJavaScriptUrl(url)) {
    273             // strip off the scheme and evaluate the string
    274             stringByEvaluatingJavaScriptFromString(
    275                     url.substring("javascript:".length()));
    276         } else {
    277             nativeLoadUrl(url, extraHeaders);
    278         }
    279         mLoadInitFromJava = false;
    280     }
    281 
    282     /**
    283      * Load a url with "POST" method from the network into the main frame.
    284      * @param url The url to load.
    285      * @param data The data for POST request.
    286      */
    287     public void postUrl(String url, byte[] data) {
    288         mLoadInitFromJava = true;
    289         nativePostUrl(url, data);
    290         mLoadInitFromJava = false;
    291     }
    292 
    293     /**
    294      * Load the content as if it was loaded by the provided base URL. The
    295      * historyUrl is used as the history entry for the load data.
    296      *
    297      * @param baseUrl Base URL used to resolve relative paths in the content
    298      * @param data Content to render in the browser
    299      * @param mimeType Mimetype of the data being passed in
    300      * @param encoding Character set encoding of the provided data.
    301      * @param historyUrl URL to use as the history entry.
    302      */
    303     public void loadData(String baseUrl, String data, String mimeType,
    304             String encoding, String historyUrl) {
    305         mLoadInitFromJava = true;
    306         if (historyUrl == null || historyUrl.length() == 0) {
    307             historyUrl = "about:blank";
    308         }
    309         if (data == null) {
    310             data = "";
    311         }
    312 
    313         // Setup defaults for missing values. These defaults where taken from
    314         // WebKit's WebFrame.mm
    315         if (baseUrl == null || baseUrl.length() == 0) {
    316             baseUrl = "about:blank";
    317         }
    318         if (mimeType == null || mimeType.length() == 0) {
    319             mimeType = "text/html";
    320         }
    321         nativeLoadData(baseUrl, data, mimeType, encoding, historyUrl);
    322         mLoadInitFromJava = false;
    323     }
    324 
    325     /**
    326      * Saves the contents of the frame as a web archive.
    327      *
    328      * @param basename The filename where the archive should be placed.
    329      * @param autoname If false, takes filename to be a file. If true, filename
    330      *                 is assumed to be a directory in which a filename will be
    331      *                 chosen according to the url of the current page.
    332      */
    333     /* package */ String saveWebArchive(String basename, boolean autoname) {
    334         return nativeSaveWebArchive(basename, autoname);
    335     }
    336 
    337     /**
    338      * Go back or forward the number of steps given.
    339      * @param steps A negative or positive number indicating the direction
    340      *              and number of steps to move.
    341      */
    342     public void goBackOrForward(int steps) {
    343         mLoadInitFromJava = true;
    344         nativeGoBackOrForward(steps);
    345         mLoadInitFromJava = false;
    346     }
    347 
    348     /**
    349      * native callback
    350      * Report an error to an activity.
    351      * @param errorCode The HTTP error code.
    352      * @param description Optional human-readable description. If no description
    353      *     is given, we'll use a standard localized error message.
    354      * @param failingUrl The URL that was being loaded when the error occurred.
    355      * TODO: Report all errors including resource errors but include some kind
    356      * of domain identifier. Change errorCode to an enum for a cleaner
    357      * interface.
    358      */
    359     private void reportError(int errorCode, String description, String failingUrl) {
    360         // As this is called for the main resource and loading will be stopped
    361         // after, reset the state variables.
    362         resetLoadingStates();
    363         if (description == null || description.isEmpty()) {
    364             description = ErrorStrings.getString(errorCode, mContext);
    365         }
    366         mCallbackProxy.onReceivedError(errorCode, description, failingUrl);
    367     }
    368 
    369     private void resetLoadingStates() {
    370         mCommitted = true;
    371         mFirstLayoutDone = true;
    372     }
    373 
    374     /* package */boolean committed() {
    375         return mCommitted;
    376     }
    377 
    378     /* package */boolean firstLayoutDone() {
    379         return mFirstLayoutDone;
    380     }
    381 
    382     /* package */int loadType() {
    383         return mLoadType;
    384     }
    385 
    386     /* package */void didFirstLayout() {
    387         if (!mFirstLayoutDone) {
    388             mFirstLayoutDone = true;
    389             // ensure {@link WebViewCore#webkitDraw} is called as we were
    390             // blocking the update in {@link #loadStarted}
    391             mWebViewCore.contentDraw();
    392         }
    393     }
    394 
    395     /**
    396      * native callback
    397      * Indicates the beginning of a new load.
    398      * This method will be called once for the main frame.
    399      */
    400     private void loadStarted(String url, Bitmap favicon, int loadType,
    401             boolean isMainFrame) {
    402         mIsMainFrame = isMainFrame;
    403 
    404         if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) {
    405             mLoadType = loadType;
    406 
    407             if (isMainFrame) {
    408                 // Call onPageStarted for main frames.
    409                 mCallbackProxy.onPageStarted(url, favicon);
    410                 // as didFirstLayout() is only called for the main frame, reset
    411                 // mFirstLayoutDone only for the main frames
    412                 mFirstLayoutDone = false;
    413                 mCommitted = false;
    414                 // remove pending draw to block update until mFirstLayoutDone is
    415                 // set to true in didFirstLayout()
    416                 mWebViewCore.clearContent();
    417                 mWebViewCore.removeMessages(WebViewCore.EventHub.WEBKIT_DRAW);
    418             }
    419         }
    420     }
    421 
    422     @SuppressWarnings("unused")
    423     private void saveFormData(HashMap<String, String> data) {
    424         if (mSettings.getSaveFormData()) {
    425             final WebHistoryItem h = mCallbackProxy.getBackForwardList()
    426                     .getCurrentItem();
    427             if (h != null) {
    428                 String url = WebTextView.urlForAutoCompleteData(h.getUrl());
    429                 if (url != null) {
    430                     mDatabase.setFormData(url, data);
    431                 }
    432             }
    433         }
    434     }
    435 
    436     @SuppressWarnings("unused")
    437     private boolean shouldSaveFormData() {
    438         if (mSettings.getSaveFormData()) {
    439             final WebHistoryItem h = mCallbackProxy.getBackForwardList()
    440                     .getCurrentItem();
    441             return h != null && h.getUrl() != null;
    442         }
    443         return false;
    444     }
    445 
    446     /**
    447      * native callback
    448      * Indicates the WebKit has committed to the new load
    449      */
    450     private void transitionToCommitted(int loadType, boolean isMainFrame) {
    451         // loadType is not used yet
    452         if (isMainFrame) {
    453             mCommitted = true;
    454             mWebViewCore.getWebViewClassic().mViewManager.postResetStateAll();
    455         }
    456     }
    457 
    458     /**
    459      * native callback
    460      * <p>
    461      * Indicates the end of a new load.
    462      * This method will be called once for the main frame.
    463      */
    464     private void loadFinished(String url, int loadType, boolean isMainFrame) {
    465         // mIsMainFrame and isMainFrame are better be equal!!!
    466 
    467         if (isMainFrame || loadType == FRAME_LOADTYPE_STANDARD) {
    468             if (isMainFrame) {
    469                 resetLoadingStates();
    470                 mCallbackProxy.switchOutDrawHistory();
    471                 mCallbackProxy.onPageFinished(url);
    472             }
    473         }
    474     }
    475 
    476     /**
    477      * Destroy all native components of the BrowserFrame.
    478      */
    479     public void destroy() {
    480         nativeDestroyFrame();
    481         mBlockMessages = true;
    482         removeCallbacksAndMessages(null);
    483     }
    484 
    485     /**
    486      * Handle messages posted to us.
    487      * @param msg The message to handle.
    488      */
    489     @Override
    490     public void handleMessage(Message msg) {
    491         if (mBlockMessages) {
    492             return;
    493         }
    494         switch (msg.what) {
    495             case FRAME_COMPLETED: {
    496                 if (mSettings.getSavePassword() && hasPasswordField()) {
    497                     WebHistoryItem item = mCallbackProxy.getBackForwardList()
    498                             .getCurrentItem();
    499                     if (item != null) {
    500                         WebAddress uri = new WebAddress(item.getUrl());
    501                         String schemePlusHost = uri.getScheme() + uri.getHost();
    502                         String[] up =
    503                                 WebViewDatabaseClassic.getInstance(mContext)
    504                                         .getUsernamePassword(schemePlusHost);
    505                         if (up != null && up[0] != null) {
    506                             setUsernamePassword(up[0], up[1]);
    507                         }
    508                     }
    509                 }
    510                 break;
    511             }
    512 
    513             case POLICY_FUNCTION: {
    514                 nativeCallPolicyFunction(msg.arg1, msg.arg2);
    515                 break;
    516             }
    517 
    518             case ORIENTATION_CHANGED: {
    519                 if (mOrientation != msg.arg1) {
    520                     mOrientation = msg.arg1;
    521                     nativeOrientationChanged(msg.arg1);
    522                 }
    523                 break;
    524             }
    525 
    526             default:
    527                 break;
    528         }
    529     }
    530 
    531     /**
    532      * Punch-through for WebCore to set the document
    533      * title. Inform the Activity of the new title.
    534      * @param title The new title of the document.
    535      */
    536     private void setTitle(String title) {
    537         // FIXME: The activity must call getTitle (a native method) to get the
    538         // title. We should try and cache the title if we can also keep it in
    539         // sync with the document.
    540         mCallbackProxy.onReceivedTitle(title);
    541     }
    542 
    543     /**
    544      * Retrieves the render tree of this frame and puts it as the object for
    545      * the message and sends the message.
    546      * @param callback the message to use to send the render tree
    547      */
    548     public void externalRepresentation(Message callback) {
    549         callback.obj = externalRepresentation();;
    550         callback.sendToTarget();
    551     }
    552 
    553     /**
    554      * Return the render tree as a string
    555      */
    556     private native String externalRepresentation();
    557 
    558     /**
    559      * Retrieves the visual text of the frames, puts it as the object for
    560      * the message and sends the message.
    561      * @param callback the message to use to send the visual text
    562      */
    563     public void documentAsText(Message callback) {
    564         StringBuilder text = new StringBuilder();
    565         if (callback.arg1 != 0) {
    566             // Dump top frame as text.
    567             text.append(documentAsText());
    568         }
    569         if (callback.arg2 != 0) {
    570             // Dump child frames as text.
    571             text.append(childFramesAsText());
    572         }
    573         callback.obj = text.toString();
    574         callback.sendToTarget();
    575     }
    576 
    577     /**
    578      * Return the text drawn on the screen as a string
    579      */
    580     private native String documentAsText();
    581 
    582     /**
    583      * Return the text drawn on the child frames as a string
    584      */
    585     private native String childFramesAsText();
    586 
    587     /*
    588      * This method is called by WebCore to inform the frame that
    589      * the Javascript window object has been cleared.
    590      * We should re-attach any attached js interfaces.
    591      */
    592     private void windowObjectCleared(int nativeFramePointer) {
    593         Iterator<String> iter = mJavaScriptObjects.keySet().iterator();
    594         while (iter.hasNext())  {
    595             String interfaceName = iter.next();
    596             JSObject jsobject = mJavaScriptObjects.get(interfaceName);
    597             if (jsobject != null && jsobject.object != null) {
    598                 nativeAddJavascriptInterface(nativeFramePointer,
    599                         jsobject.object, interfaceName, jsobject.requireAnnotation);
    600             }
    601         }
    602         mRemovedJavaScriptObjects.clear();
    603     }
    604 
    605     /*
    606      * Add javascript objects to the internal list of objects. The default behavior
    607      * is to allow access to inherited methods (no annotation needed). This is only
    608      * used when js objects are passed through a constructor (via a hidden constructor).
    609      *
    610      * @TODO change the default behavior to be compatible with the public addjavascriptinterface
    611      */
    612     private void addJavaScriptObjects(Map<String, Object> javascriptInterfaces) {
    613 
    614         // TODO in a separate CL provide logic to enable annotations for API level JB_MR1 and above.
    615         if (javascriptInterfaces == null) return;
    616         Iterator<String> iter = javascriptInterfaces.keySet().iterator();
    617         while (iter.hasNext())  {
    618             String interfaceName = iter.next();
    619             Object object = javascriptInterfaces.get(interfaceName);
    620             if (object != null) {
    621                 mJavaScriptObjects.put(interfaceName, new JSObject(object, false));
    622             }
    623         }
    624     }
    625 
    626     /**
    627      * This method is called by WebCore to check whether application
    628      * wants to hijack url loading
    629      */
    630     public boolean handleUrl(String url) {
    631         if (mLoadInitFromJava == true) {
    632             return false;
    633         }
    634         if (mCallbackProxy.shouldOverrideUrlLoading(url)) {
    635             // if the url is hijacked, reset the state of the BrowserFrame
    636             didFirstLayout();
    637             return true;
    638         } else {
    639             return false;
    640         }
    641     }
    642 
    643     public void addJavascriptInterface(Object obj, String interfaceName,
    644             boolean requireAnnotation) {
    645         assert obj != null;
    646         removeJavascriptInterface(interfaceName);
    647         mJavaScriptObjects.put(interfaceName, new JSObject(obj, requireAnnotation));
    648     }
    649 
    650     public void removeJavascriptInterface(String interfaceName) {
    651         // We keep a reference to the removed object because the native side holds only a weak
    652         // reference and we need to allow the object to continue to be used until the page has been
    653         // navigated.
    654         if (mJavaScriptObjects.containsKey(interfaceName)) {
    655             mRemovedJavaScriptObjects.add(mJavaScriptObjects.remove(interfaceName));
    656         }
    657     }
    658 
    659     /**
    660      * Called by JNI.  Given a URI, find the associated file and return its size
    661      * @param uri A String representing the URI of the desired file.
    662      * @return int The size of the given file.
    663      */
    664     private int getFileSize(String uri) {
    665         int size = 0;
    666         try {
    667             InputStream stream = mContext.getContentResolver()
    668                             .openInputStream(Uri.parse(uri));
    669             size = stream.available();
    670             stream.close();
    671         } catch (Exception e) {}
    672         return size;
    673     }
    674 
    675     /**
    676      * Called by JNI.  Given a URI, a buffer, and an offset into the buffer,
    677      * copy the resource into buffer.
    678      * @param uri A String representing the URI of the desired file.
    679      * @param buffer The byte array to copy the data into.
    680      * @param offset The offet into buffer to place the data.
    681      * @param expectedSize The size that the buffer has allocated for this file.
    682      * @return int The size of the given file, or zero if it fails.
    683      */
    684     private int getFile(String uri, byte[] buffer, int offset,
    685             int expectedSize) {
    686         int size = 0;
    687         try {
    688             InputStream stream = mContext.getContentResolver()
    689                             .openInputStream(Uri.parse(uri));
    690             size = stream.available();
    691             if (size <= expectedSize && buffer != null
    692                     && buffer.length - offset >= size) {
    693                 stream.read(buffer, offset, size);
    694             } else {
    695                 size = 0;
    696             }
    697             stream.close();
    698         } catch (java.io.FileNotFoundException e) {
    699             Log.e(LOGTAG, "FileNotFoundException:" + e);
    700             size = 0;
    701         } catch (java.io.IOException e2) {
    702             Log.e(LOGTAG, "IOException: " + e2);
    703             size = 0;
    704         }
    705         return size;
    706     }
    707 
    708     /**
    709      * Get the InputStream for an Android resource
    710      * There are three different kinds of android resources:
    711      * - file:///android_res
    712      * - file:///android_asset
    713      * - content://
    714      * @param url The url to load.
    715      * @return An InputStream to the android resource
    716      */
    717     private InputStream inputStreamForAndroidResource(String url) {
    718         final String ANDROID_ASSET = URLUtil.ASSET_BASE;
    719         final String ANDROID_RESOURCE = URLUtil.RESOURCE_BASE;
    720         final String ANDROID_CONTENT = URLUtil.CONTENT_BASE;
    721 
    722         if (url.startsWith(ANDROID_RESOURCE)) {
    723             url = url.replaceFirst(ANDROID_RESOURCE, "");
    724             if (url == null || url.length() == 0) {
    725                 Log.e(LOGTAG, "url has length 0 " + url);
    726                 return null;
    727             }
    728             int slash = url.indexOf('/');
    729             int dot = url.indexOf('.', slash);
    730             if (slash == -1 || dot == -1) {
    731                 Log.e(LOGTAG, "Incorrect res path: " + url);
    732                 return null;
    733             }
    734             String subClassName = url.substring(0, slash);
    735             String fieldName = url.substring(slash + 1, dot);
    736             String errorMsg = null;
    737             try {
    738                 final Class<?> d = mContext.getApplicationContext()
    739                         .getClassLoader().loadClass(
    740                                 mContext.getPackageName() + ".R$"
    741                                         + subClassName);
    742                 final java.lang.reflect.Field field = d.getField(fieldName);
    743                 final int id = field.getInt(null);
    744                 TypedValue value = new TypedValue();
    745                 mContext.getResources().getValue(id, value, true);
    746                 if (value.type == TypedValue.TYPE_STRING) {
    747                     return mContext.getAssets().openNonAsset(
    748                             value.assetCookie, value.string.toString(),
    749                             AssetManager.ACCESS_STREAMING);
    750                 } else {
    751                     // Old stack only supports TYPE_STRING for res files
    752                     Log.e(LOGTAG, "not of type string: " + url);
    753                     return null;
    754                 }
    755             } catch (Exception e) {
    756                 Log.e(LOGTAG, "Exception: " + url);
    757                 return null;
    758             }
    759         } else if (url.startsWith(ANDROID_ASSET)) {
    760             String assetUrl = url.replaceFirst(ANDROID_ASSET, "");
    761             try {
    762                 AssetManager assets = mContext.getAssets();
    763                 Uri uri = Uri.parse(assetUrl);
    764                 return assets.open(uri.getPath(), AssetManager.ACCESS_STREAMING);
    765             } catch (IOException e) {
    766                 return null;
    767             } catch (Exception e) {
    768                 Log.w(LOGTAG, "Problem loading url: " + url, e);
    769                 return null;
    770             }
    771         } else if (mSettings.getAllowContentAccess() &&
    772                    url.startsWith(ANDROID_CONTENT)) {
    773             try {
    774                 // Strip off MIME type. If we don't do this, we can fail to
    775                 // load Gmail attachments, because the URL being loaded doesn't
    776                 // exactly match the URL we have permission to read.
    777                 int mimeIndex = url.lastIndexOf('?');
    778                 if (mimeIndex != -1) {
    779                     url = url.substring(0, mimeIndex);
    780                 }
    781                 Uri uri = Uri.parse(url);
    782                 return mContext.getContentResolver().openInputStream(uri);
    783             } catch (Exception e) {
    784                 Log.e(LOGTAG, "Exception: " + url);
    785                 return null;
    786             }
    787         } else {
    788             return null;
    789         }
    790     }
    791 
    792     /**
    793      * If this looks like a POST request (form submission) containing a username
    794      * and password, give the user the option of saving them. Will either do
    795      * nothing, or block until the UI interaction is complete.
    796      *
    797      * Called directly by WebKit.
    798      *
    799      * @param postData The data about to be sent as the body of a POST request.
    800      * @param username The username entered by the user (sniffed from the DOM).
    801      * @param password The password entered by the user (sniffed from the DOM).
    802      */
    803     private void maybeSavePassword(
    804             byte[] postData, String username, String password) {
    805         if (postData == null
    806                 || username == null || username.isEmpty()
    807                 || password == null || password.isEmpty()) {
    808             return; // No password to save.
    809         }
    810 
    811         if (!mSettings.getSavePassword()) {
    812             return; // User doesn't want to save passwords.
    813         }
    814 
    815         try {
    816             if (DebugFlags.BROWSER_FRAME) {
    817                 Assert.assertNotNull(mCallbackProxy.getBackForwardList()
    818                         .getCurrentItem());
    819             }
    820             WebAddress uri = new WebAddress(mCallbackProxy
    821                     .getBackForwardList().getCurrentItem().getUrl());
    822             String schemePlusHost = uri.getScheme() + uri.getHost();
    823             // Check to see if the username & password appear in
    824             // the post data (there could be another form on the
    825             // page and that was posted instead.
    826             String postString = new String(postData);
    827             if (postString.contains(URLEncoder.encode(username)) &&
    828                     postString.contains(URLEncoder.encode(password))) {
    829                 String[] saved = mDatabase.getUsernamePassword(
    830                         schemePlusHost);
    831                 if (saved != null) {
    832                     // null username implies that user has chosen not to
    833                     // save password
    834                     if (saved[0] != null) {
    835                         // non-null username implies that user has
    836                         // chosen to save password, so update the
    837                         // recorded password
    838                         mDatabase.setUsernamePassword(schemePlusHost, username, password);
    839                     }
    840                 } else {
    841                     // CallbackProxy will handle creating the resume
    842                     // message
    843                     mCallbackProxy.onSavePassword(schemePlusHost, username,
    844                             password, null);
    845                 }
    846             }
    847         } catch (ParseException ex) {
    848             // if it is bad uri, don't save its password
    849         }
    850     }
    851 
    852     // Called by jni from the chrome network stack.
    853     private WebResourceResponse shouldInterceptRequest(String url) {
    854         InputStream androidResource = inputStreamForAndroidResource(url);
    855         if (androidResource != null) {
    856             return new WebResourceResponse(null, null, androidResource);
    857         }
    858 
    859         // Note that we check this after looking for an android_asset or
    860         // android_res URL, as we allow those even if file access is disabled.
    861         if (!mSettings.getAllowFileAccess() && url.startsWith("file://")) {
    862             return new WebResourceResponse(null, null, null);
    863         }
    864 
    865         WebResourceResponse response = mCallbackProxy.shouldInterceptRequest(url);
    866         if (response == null && "browser:incognito".equals(url)) {
    867             try {
    868                 Resources res = mContext.getResources();
    869                 InputStream ins = res.openRawResource(
    870                         com.android.internal.R.raw.incognito_mode_start_page);
    871                 response = new WebResourceResponse("text/html", "utf8", ins);
    872             } catch (NotFoundException ex) {
    873                 // This shouldn't happen, but try and gracefully handle it jic
    874                 Log.w(LOGTAG, "Failed opening raw.incognito_mode_start_page", ex);
    875             }
    876         }
    877         return response;
    878     }
    879 
    880     /**
    881      * Set the progress for the browser activity.  Called by native code.
    882      * Uses a delay so it does not happen too often.
    883      * @param newProgress An int between zero and one hundred representing
    884      *                    the current progress percentage of loading the page.
    885      */
    886     private void setProgress(int newProgress) {
    887         mCallbackProxy.onProgressChanged(newProgress);
    888         if (newProgress == 100) {
    889             sendMessageDelayed(obtainMessage(FRAME_COMPLETED), 100);
    890         }
    891         // FIXME: Need to figure out a better way to switch out of the history
    892         // drawing mode. Maybe we can somehow compare the history picture with
    893         // the current picture, and switch when it contains more content.
    894         if (mFirstLayoutDone && newProgress > TRANSITION_SWITCH_THRESHOLD) {
    895             mCallbackProxy.switchOutDrawHistory();
    896         }
    897     }
    898 
    899     /**
    900      * Send the icon to the activity for display.
    901      * @param icon A Bitmap representing a page's favicon.
    902      */
    903     private void didReceiveIcon(Bitmap icon) {
    904         mCallbackProxy.onReceivedIcon(icon);
    905     }
    906 
    907     // Called by JNI when an apple-touch-icon attribute was found.
    908     private void didReceiveTouchIconUrl(String url, boolean precomposed) {
    909         mCallbackProxy.onReceivedTouchIconUrl(url, precomposed);
    910     }
    911 
    912     /**
    913      * Request a new window from the client.
    914      * @return The BrowserFrame object stored in the new WebView.
    915      */
    916     private BrowserFrame createWindow(boolean dialog, boolean userGesture) {
    917         return mCallbackProxy.createWindow(dialog, userGesture);
    918     }
    919 
    920     /**
    921      * Try to focus this WebView.
    922      */
    923     private void requestFocus() {
    924         mCallbackProxy.onRequestFocus();
    925     }
    926 
    927     /**
    928      * Close this frame and window.
    929      */
    930     private void closeWindow(WebViewCore w) {
    931         mCallbackProxy.onCloseWindow(w.getWebViewClassic());
    932     }
    933 
    934     // XXX: Must match PolicyAction in FrameLoaderTypes.h in webcore
    935     static final int POLICY_USE = 0;
    936     static final int POLICY_IGNORE = 2;
    937 
    938     private void decidePolicyForFormResubmission(int policyFunction) {
    939         Message dontResend = obtainMessage(POLICY_FUNCTION, policyFunction,
    940                 POLICY_IGNORE);
    941         Message resend = obtainMessage(POLICY_FUNCTION, policyFunction,
    942                 POLICY_USE);
    943         mCallbackProxy.onFormResubmission(dontResend, resend);
    944     }
    945 
    946     /**
    947      * Tell the activity to update its global history.
    948      */
    949     private void updateVisitedHistory(String url, boolean isReload) {
    950         mCallbackProxy.doUpdateVisitedHistory(url, isReload);
    951     }
    952 
    953     /**
    954      * Get the CallbackProxy for sending messages to the UI thread.
    955      */
    956     /* package */ CallbackProxy getCallbackProxy() {
    957         return mCallbackProxy;
    958     }
    959 
    960     /**
    961      * Returns the User Agent used by this frame
    962      */
    963     String getUserAgentString() {
    964         return mSettings.getUserAgentString();
    965     }
    966 
    967     // These ids need to be in sync with enum rawResId in PlatformBridge.h
    968     private static final int NODOMAIN = 1;
    969     private static final int LOADERROR = 2;
    970     /* package */ static final int DRAWABLEDIR = 3;
    971     private static final int FILE_UPLOAD_LABEL = 4;
    972     private static final int RESET_LABEL = 5;
    973     private static final int SUBMIT_LABEL = 6;
    974     private static final int FILE_UPLOAD_NO_FILE_CHOSEN = 7;
    975 
    976     private String getRawResFilename(int id) {
    977         return getRawResFilename(id, mContext);
    978     }
    979     /* package */ static String getRawResFilename(int id, Context context) {
    980         int resid;
    981         switch (id) {
    982             case NODOMAIN:
    983                 resid = com.android.internal.R.raw.nodomain;
    984                 break;
    985 
    986             case LOADERROR:
    987                 resid = com.android.internal.R.raw.loaderror;
    988                 break;
    989 
    990             case DRAWABLEDIR:
    991                 // use one known resource to find the drawable directory
    992                 resid = com.android.internal.R.drawable.btn_check_off;
    993                 break;
    994 
    995             case FILE_UPLOAD_LABEL:
    996                 return context.getResources().getString(
    997                         com.android.internal.R.string.upload_file);
    998 
    999             case RESET_LABEL:
   1000                 return context.getResources().getString(
   1001                         com.android.internal.R.string.reset);
   1002 
   1003             case SUBMIT_LABEL:
   1004                 return context.getResources().getString(
   1005                         com.android.internal.R.string.submit);
   1006 
   1007             case FILE_UPLOAD_NO_FILE_CHOSEN:
   1008                 return context.getResources().getString(
   1009                         com.android.internal.R.string.no_file_chosen);
   1010 
   1011             default:
   1012                 Log.e(LOGTAG, "getRawResFilename got incompatible resource ID");
   1013                 return "";
   1014         }
   1015         TypedValue value = new TypedValue();
   1016         context.getResources().getValue(resid, value, true);
   1017         if (id == DRAWABLEDIR) {
   1018             String path = value.string.toString();
   1019             int index = path.lastIndexOf('/');
   1020             if (index < 0) {
   1021                 Log.e(LOGTAG, "Can't find drawable directory.");
   1022                 return "";
   1023             }
   1024             return path.substring(0, index + 1);
   1025         }
   1026         return value.string.toString();
   1027     }
   1028 
   1029     private float density() {
   1030         return WebViewCore.getFixedDisplayDensity(mContext);
   1031     }
   1032 
   1033     /**
   1034      * Called by JNI when the native HTTP stack gets an authentication request.
   1035      *
   1036      * We delegate the request to CallbackProxy, and route its response to
   1037      * {@link #nativeAuthenticationProceed(int, String, String)} or
   1038      * {@link #nativeAuthenticationCancel(int)}.
   1039      *
   1040      * We don't care what thread the callback is invoked on. All threading is
   1041      * handled on the C++ side, because the WebKit thread may be blocked on a
   1042      * synchronous call and unable to pump our MessageQueue.
   1043      */
   1044     private void didReceiveAuthenticationChallenge(
   1045             final int handle, String host, String realm, final boolean useCachedCredentials,
   1046             final boolean suppressDialog) {
   1047 
   1048         HttpAuthHandler handler = new HttpAuthHandler() {
   1049 
   1050             @Override
   1051             public boolean useHttpAuthUsernamePassword() {
   1052                 return useCachedCredentials;
   1053             }
   1054 
   1055             @Override
   1056             public void proceed(String username, String password) {
   1057                 nativeAuthenticationProceed(handle, username, password);
   1058             }
   1059 
   1060             @Override
   1061             public void cancel() {
   1062                 nativeAuthenticationCancel(handle);
   1063             }
   1064 
   1065             @Override
   1066             public boolean suppressDialog() {
   1067                 return suppressDialog;
   1068             }
   1069         };
   1070         mCallbackProxy.onReceivedHttpAuthRequest(handler, host, realm);
   1071     }
   1072 
   1073     /**
   1074      * Called by JNI when the Chromium HTTP stack gets an invalid certificate chain.
   1075      *
   1076      * We delegate the request to CallbackProxy, and route its response to
   1077      * {@link #nativeSslCertErrorProceed(int)} or
   1078      * {@link #nativeSslCertErrorCancel(int, int)}.
   1079      */
   1080     private void reportSslCertError(final int handle, final int certError, byte certDER[],
   1081             String url) {
   1082         final SslError sslError;
   1083         try {
   1084             X509Certificate cert = new X509CertImpl(certDER);
   1085             SslCertificate sslCert = new SslCertificate(cert);
   1086             sslError = SslError.SslErrorFromChromiumErrorCode(certError, sslCert, url);
   1087         } catch (IOException e) {
   1088             // Can't get the certificate, not much to do.
   1089             Log.e(LOGTAG, "Can't get the certificate from WebKit, canceling");
   1090             nativeSslCertErrorCancel(handle, certError);
   1091             return;
   1092         }
   1093 
   1094         if (SslCertLookupTable.getInstance().isAllowed(sslError)) {
   1095             nativeSslCertErrorProceed(handle);
   1096             mCallbackProxy.onProceededAfterSslError(sslError);
   1097             return;
   1098         }
   1099 
   1100         SslErrorHandler handler = new SslErrorHandler() {
   1101             @Override
   1102             public void proceed() {
   1103                 SslCertLookupTable.getInstance().setIsAllowed(sslError);
   1104                 post(new Runnable() {
   1105                         public void run() {
   1106                             nativeSslCertErrorProceed(handle);
   1107                         }
   1108                     });
   1109             }
   1110             @Override
   1111             public void cancel() {
   1112                 post(new Runnable() {
   1113                         public void run() {
   1114                             nativeSslCertErrorCancel(handle, certError);
   1115                         }
   1116                     });
   1117             }
   1118         };
   1119         mCallbackProxy.onReceivedSslError(handler, sslError);
   1120     }
   1121 
   1122     /**
   1123      * Called by JNI when the native HTTPS stack gets a client
   1124      * certificate request.
   1125      *
   1126      * We delegate the request to CallbackProxy, and route its response to
   1127      * {@link #nativeSslClientCert(int, X509Certificate)}.
   1128      */
   1129     private void requestClientCert(int handle, String hostAndPort) {
   1130         SslClientCertLookupTable table = SslClientCertLookupTable.getInstance();
   1131         if (table.IsAllowed(hostAndPort)) {
   1132             // previously allowed
   1133             PrivateKey pkey = table.PrivateKey(hostAndPort);
   1134             if (pkey instanceof OpenSSLKeyHolder) {
   1135                 OpenSSLKey sslKey = ((OpenSSLKeyHolder) pkey).getOpenSSLKey();
   1136                 nativeSslClientCert(handle,
   1137                                     sslKey.getPkeyContext(),
   1138                                     table.CertificateChain(hostAndPort));
   1139             } else {
   1140                 nativeSslClientCert(handle,
   1141                                     pkey.getEncoded(),
   1142                                     table.CertificateChain(hostAndPort));
   1143             }
   1144         } else if (table.IsDenied(hostAndPort)) {
   1145             // previously denied
   1146             nativeSslClientCert(handle, 0, null);
   1147         } else {
   1148             // previously ignored or new
   1149             mCallbackProxy.onReceivedClientCertRequest(
   1150                     new ClientCertRequestHandler(this, handle, hostAndPort, table), hostAndPort);
   1151         }
   1152     }
   1153 
   1154     /**
   1155      * Called by JNI when the native HTTP stack needs to download a file.
   1156      *
   1157      * We delegate the request to CallbackProxy, which owns the current app's
   1158      * DownloadListener.
   1159      */
   1160     private void downloadStart(String url, String userAgent,
   1161             String contentDisposition, String mimeType, String referer, long contentLength) {
   1162         // This will only work if the url ends with the filename
   1163         if (mimeType.isEmpty()) {
   1164             try {
   1165                 String extension = url.substring(url.lastIndexOf('.') + 1);
   1166                 mimeType = libcore.net.MimeUtils.guessMimeTypeFromExtension(extension);
   1167                 // MimeUtils might return null, not sure if downloadmanager is happy with that
   1168                 if (mimeType == null)
   1169                     mimeType = "";
   1170             } catch(IndexOutOfBoundsException exception) {
   1171                 // mimeType string end with a '.', not much to do
   1172             }
   1173         }
   1174         mimeType = MimeTypeMap.getSingleton().remapGenericMimeType(
   1175                 mimeType, url, contentDisposition);
   1176 
   1177         if (CertTool.getCertType(mimeType) != null) {
   1178             mKeyStoreHandler = new KeyStoreHandler(mimeType);
   1179         } else {
   1180             mCallbackProxy.onDownloadStart(url, userAgent,
   1181                 contentDisposition, mimeType, referer, contentLength);
   1182         }
   1183     }
   1184 
   1185     /**
   1186      * Called by JNI for Chrome HTTP stack when the Java side needs to access the data.
   1187      */
   1188     private void didReceiveData(byte data[], int size) {
   1189         if (mKeyStoreHandler != null) mKeyStoreHandler.didReceiveData(data, size);
   1190     }
   1191 
   1192     private void didFinishLoading() {
   1193       if (mKeyStoreHandler != null) {
   1194           mKeyStoreHandler.installCert(mContext);
   1195           mKeyStoreHandler = null;
   1196       }
   1197     }
   1198 
   1199     /**
   1200      * Called by JNI when we recieve a certificate for the page's main resource.
   1201      * Used by the Chromium HTTP stack only.
   1202      */
   1203     private void setCertificate(byte cert_der[]) {
   1204         try {
   1205             X509Certificate cert = new X509CertImpl(cert_der);
   1206             mCallbackProxy.onReceivedCertificate(new SslCertificate(cert));
   1207         } catch (IOException e) {
   1208             // Can't get the certificate, not much to do.
   1209             Log.e(LOGTAG, "Can't get the certificate from WebKit, canceling");
   1210             return;
   1211         }
   1212     }
   1213 
   1214     /**
   1215      * Called by JNI when processing the X-Auto-Login header.
   1216      */
   1217     private void autoLogin(String realm, String account, String args) {
   1218         mCallbackProxy.onReceivedLoginRequest(realm, account, args);
   1219     }
   1220 
   1221     //==========================================================================
   1222     // native functions
   1223     //==========================================================================
   1224 
   1225     /**
   1226      * Create a new native frame for a given WebView
   1227      * @param w     A WebView that the frame draws into.
   1228      * @param am    AssetManager to use to get assets.
   1229      * @param list  The native side will add and remove items from this list as
   1230      *              the native list changes.
   1231      */
   1232     private native void nativeCreateFrame(WebViewCore w, AssetManager am,
   1233             WebBackForwardList list);
   1234 
   1235     /**
   1236      * Destroy the native frame.
   1237      */
   1238     public native void nativeDestroyFrame();
   1239 
   1240     private native void nativeCallPolicyFunction(int policyFunction,
   1241             int decision);
   1242 
   1243     /**
   1244      * Reload the current main frame.
   1245      */
   1246     public native void reload(boolean allowStale);
   1247 
   1248     /**
   1249      * Go back or forward the number of steps given.
   1250      * @param steps A negative or positive number indicating the direction
   1251      *              and number of steps to move.
   1252      */
   1253     private native void nativeGoBackOrForward(int steps);
   1254 
   1255     /**
   1256      * stringByEvaluatingJavaScriptFromString will execute the
   1257      * JS passed in in the context of this browser frame.
   1258      * @param script A javascript string to execute
   1259      *
   1260      * @return string result of execution or null
   1261      */
   1262     public native String stringByEvaluatingJavaScriptFromString(String script);
   1263 
   1264     /**
   1265      * Add a javascript interface to the main frame.
   1266      */
   1267     private native void nativeAddJavascriptInterface(int nativeFramePointer,
   1268             Object obj, String interfaceName, boolean requireAnnotation);
   1269 
   1270     public native void clearCache();
   1271 
   1272     /**
   1273      * Returns false if the url is bad.
   1274      */
   1275     private native void nativeLoadUrl(String url, Map<String, String> headers);
   1276 
   1277     private native void nativePostUrl(String url, byte[] postData);
   1278 
   1279     private native void nativeLoadData(String baseUrl, String data,
   1280             String mimeType, String encoding, String historyUrl);
   1281 
   1282     /**
   1283      * Stop loading the current page.
   1284      */
   1285     public void stopLoading() {
   1286         if (mIsMainFrame) {
   1287             resetLoadingStates();
   1288         }
   1289         nativeStopLoading();
   1290     }
   1291 
   1292     private native void nativeStopLoading();
   1293 
   1294     /**
   1295      * Return true if the document has images.
   1296      */
   1297     public native boolean documentHasImages();
   1298 
   1299     /**
   1300      * @return TRUE if there is a password field in the current frame
   1301      */
   1302     private native boolean hasPasswordField();
   1303 
   1304     /**
   1305      * Get username and password in the current frame. If found, String[0] is
   1306      * username and String[1] is password. Otherwise return NULL.
   1307      * @return String[]
   1308      */
   1309     private native String[] getUsernamePassword();
   1310 
   1311     /**
   1312      * Set username and password to the proper fields in the current frame
   1313      * @param username
   1314      * @param password
   1315      */
   1316     private native void setUsernamePassword(String username, String password);
   1317 
   1318     private native String nativeSaveWebArchive(String basename, boolean autoname);
   1319 
   1320     private native void nativeOrientationChanged(int orientation);
   1321 
   1322     private native void nativeAuthenticationProceed(int handle, String username, String password);
   1323     private native void nativeAuthenticationCancel(int handle);
   1324 
   1325     private native void nativeSslCertErrorProceed(int handle);
   1326     private native void nativeSslCertErrorCancel(int handle, int certError);
   1327 
   1328     native void nativeSslClientCert(int handle,
   1329                                     long ctx,
   1330                                     byte[][] asn1DerEncodedCertificateChain);
   1331 
   1332     native void nativeSslClientCert(int handle,
   1333                                     byte[] pkey,
   1334                                     byte[][] asn1DerEncodedCertificateChain);
   1335 
   1336     /**
   1337      * Returns true when the contents of the frame is an RTL or vertical-rl
   1338      * page. This is used for determining whether a frame should be initially
   1339      * scrolled right-most as opposed to left-most.
   1340      * @return true when the frame should be initially scrolled right-most
   1341      * based on the text direction and writing mode.
   1342      */
   1343     /* package */ boolean getShouldStartScrolledRight() {
   1344         return nativeGetShouldStartScrolledRight(mNativeFrame);
   1345     }
   1346 
   1347     private native boolean nativeGetShouldStartScrolledRight(int nativeBrowserFrame);
   1348 }
   1349