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