Home | History | Annotate | Download | only in browser
      1 /*
      2  * Copyright (C) 2009 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 com.android.browser;
     18 
     19 import android.app.Activity;
     20 import android.app.AlertDialog;
     21 import android.content.ContentResolver;
     22 import android.content.ContentValues;
     23 import android.content.Context;
     24 import android.content.DialogInterface;
     25 import android.content.DialogInterface.OnCancelListener;
     26 import android.graphics.Bitmap;
     27 import android.graphics.Bitmap.CompressFormat;
     28 import android.graphics.BitmapFactory;
     29 import android.graphics.Canvas;
     30 import android.graphics.Color;
     31 import android.graphics.Paint;
     32 import android.graphics.Picture;
     33 import android.graphics.PorterDuff;
     34 import android.graphics.PorterDuffXfermode;
     35 import android.net.Uri;
     36 import android.net.http.SslError;
     37 import android.os.Bundle;
     38 import android.os.Handler;
     39 import android.os.Message;
     40 import android.os.SystemClock;
     41 import android.security.KeyChain;
     42 import android.security.KeyChainAliasCallback;
     43 import android.text.TextUtils;
     44 import android.util.Log;
     45 import android.view.KeyEvent;
     46 import android.view.LayoutInflater;
     47 import android.view.View;
     48 import android.view.ViewStub;
     49 import android.webkit.BrowserDownloadListener;
     50 import android.webkit.ClientCertRequestHandler;
     51 import android.webkit.ConsoleMessage;
     52 import android.webkit.GeolocationPermissions;
     53 import android.webkit.HttpAuthHandler;
     54 import android.webkit.SslErrorHandler;
     55 import android.webkit.URLUtil;
     56 import android.webkit.ValueCallback;
     57 import android.webkit.WebBackForwardList;
     58 import android.webkit.WebBackForwardListClient;
     59 import android.webkit.WebChromeClient;
     60 import android.webkit.WebHistoryItem;
     61 import android.webkit.WebResourceResponse;
     62 import android.webkit.WebStorage;
     63 import android.webkit.WebView;
     64 import android.webkit.WebView.PictureListener;
     65 import android.webkit.WebViewClassic;
     66 import android.webkit.WebViewClient;
     67 import android.webkit.WebViewClientClassicExt;
     68 import android.widget.CheckBox;
     69 import android.widget.Toast;
     70 
     71 import com.android.browser.TabControl.OnThumbnailUpdatedListener;
     72 import com.android.browser.homepages.HomeProvider;
     73 import com.android.browser.provider.SnapshotProvider.Snapshots;
     74 
     75 import java.io.ByteArrayOutputStream;
     76 import java.io.File;
     77 import java.io.IOException;
     78 import java.io.OutputStream;
     79 import java.nio.ByteBuffer;
     80 import java.util.LinkedList;
     81 import java.util.Map;
     82 import java.util.UUID;
     83 import java.util.Vector;
     84 import java.util.regex.Pattern;
     85 import java.util.zip.GZIPOutputStream;
     86 
     87 /**
     88  * Class for maintaining Tabs with a main WebView and a subwindow.
     89  */
     90 class Tab implements PictureListener {
     91 
     92     // Log Tag
     93     private static final String LOGTAG = "Tab";
     94     private static final boolean LOGD_ENABLED = com.android.browser.Browser.LOGD_ENABLED;
     95     // Special case the logtag for messages for the Console to make it easier to
     96     // filter them and match the logtag used for these messages in older versions
     97     // of the browser.
     98     private static final String CONSOLE_LOGTAG = "browser";
     99 
    100     private static final int MSG_CAPTURE = 42;
    101     private static final int CAPTURE_DELAY = 100;
    102     private static final int INITIAL_PROGRESS = 5;
    103 
    104     private static Bitmap sDefaultFavicon;
    105 
    106     private static Paint sAlphaPaint = new Paint();
    107     static {
    108         sAlphaPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
    109         sAlphaPaint.setColor(Color.TRANSPARENT);
    110     }
    111 
    112     public enum SecurityState {
    113         // The page's main resource does not use SSL. Note that we use this
    114         // state irrespective of the SSL authentication state of sub-resources.
    115         SECURITY_STATE_NOT_SECURE,
    116         // The page's main resource uses SSL and the certificate is good. The
    117         // same is true of all sub-resources.
    118         SECURITY_STATE_SECURE,
    119         // The page's main resource uses SSL and the certificate is good, but
    120         // some sub-resources either do not use SSL or have problems with their
    121         // certificates.
    122         SECURITY_STATE_MIXED,
    123         // The page's main resource uses SSL but there is a problem with its
    124         // certificate.
    125         SECURITY_STATE_BAD_CERTIFICATE,
    126     }
    127 
    128     Context mContext;
    129     protected WebViewController mWebViewController;
    130 
    131     // The tab ID
    132     private long mId = -1;
    133 
    134     // The Geolocation permissions prompt
    135     private GeolocationPermissionsPrompt mGeolocationPermissionsPrompt;
    136     // Main WebView wrapper
    137     private View mContainer;
    138     // Main WebView
    139     private WebView mMainView;
    140     // Subwindow container
    141     private View mSubViewContainer;
    142     // Subwindow WebView
    143     private WebView mSubView;
    144     // Saved bundle for when we are running low on memory. It contains the
    145     // information needed to restore the WebView if the user goes back to the
    146     // tab.
    147     private Bundle mSavedState;
    148     // Parent Tab. This is the Tab that created this Tab, or null if the Tab was
    149     // created by the UI
    150     private Tab mParent;
    151     // Tab that constructed by this Tab. This is used when this Tab is
    152     // destroyed, it clears all mParentTab values in the children.
    153     private Vector<Tab> mChildren;
    154     // If true, the tab is in the foreground of the current activity.
    155     private boolean mInForeground;
    156     // If true, the tab is in page loading state (after onPageStarted,
    157     // before onPageFinsihed)
    158     private boolean mInPageLoad;
    159     private boolean mDisableOverrideUrlLoading;
    160     // The last reported progress of the current page
    161     private int mPageLoadProgress;
    162     // The time the load started, used to find load page time
    163     private long mLoadStartTime;
    164     // Application identifier used to find tabs that another application wants
    165     // to reuse.
    166     private String mAppId;
    167     // flag to indicate if tab should be closed on back
    168     private boolean mCloseOnBack;
    169     // Keep the original url around to avoid killing the old WebView if the url
    170     // has not changed.
    171     // Error console for the tab
    172     private ErrorConsoleView mErrorConsole;
    173     // The listener that gets invoked when a download is started from the
    174     // mMainView
    175     private final BrowserDownloadListener mDownloadListener;
    176     // Listener used to know when we move forward or back in the history list.
    177     private final WebBackForwardListClient mWebBackForwardListClient;
    178     private DataController mDataController;
    179     // State of the auto-login request.
    180     private DeviceAccountLogin mDeviceAccountLogin;
    181 
    182     // AsyncTask for downloading touch icons
    183     DownloadTouchIcon mTouchIconLoader;
    184 
    185     private BrowserSettings mSettings;
    186     private int mCaptureWidth;
    187     private int mCaptureHeight;
    188     private Bitmap mCapture;
    189     private Handler mHandler;
    190     private boolean mUpdateThumbnail;
    191 
    192     /**
    193      * See {@link #clearBackStackWhenItemAdded(String)}.
    194      */
    195     private Pattern mClearHistoryUrlPattern;
    196 
    197     private static synchronized Bitmap getDefaultFavicon(Context context) {
    198         if (sDefaultFavicon == null) {
    199             sDefaultFavicon = BitmapFactory.decodeResource(
    200                     context.getResources(), R.drawable.app_web_browser_sm);
    201         }
    202         return sDefaultFavicon;
    203     }
    204 
    205     // All the state needed for a page
    206     protected static class PageState {
    207         String mUrl;
    208         String mOriginalUrl;
    209         String mTitle;
    210         SecurityState mSecurityState;
    211         // This is non-null only when mSecurityState is SECURITY_STATE_BAD_CERTIFICATE.
    212         SslError mSslCertificateError;
    213         Bitmap mFavicon;
    214         boolean mIsBookmarkedSite;
    215         boolean mIncognito;
    216 
    217         PageState(Context c, boolean incognito) {
    218             mIncognito = incognito;
    219             if (mIncognito) {
    220                 mOriginalUrl = mUrl = "browser:incognito";
    221                 mTitle = c.getString(R.string.new_incognito_tab);
    222             } else {
    223                 mOriginalUrl = mUrl = "";
    224                 mTitle = c.getString(R.string.new_tab);
    225             }
    226             mSecurityState = SecurityState.SECURITY_STATE_NOT_SECURE;
    227         }
    228 
    229         PageState(Context c, boolean incognito, String url, Bitmap favicon) {
    230             mIncognito = incognito;
    231             mOriginalUrl = mUrl = url;
    232             if (URLUtil.isHttpsUrl(url)) {
    233                 mSecurityState = SecurityState.SECURITY_STATE_SECURE;
    234             } else {
    235                 mSecurityState = SecurityState.SECURITY_STATE_NOT_SECURE;
    236             }
    237             mFavicon = favicon;
    238         }
    239 
    240     }
    241 
    242     // The current/loading page's state
    243     protected PageState mCurrentState;
    244 
    245     // Used for saving and restoring each Tab
    246     static final String ID = "ID";
    247     static final String CURRURL = "currentUrl";
    248     static final String CURRTITLE = "currentTitle";
    249     static final String PARENTTAB = "parentTab";
    250     static final String APPID = "appid";
    251     static final String INCOGNITO = "privateBrowsingEnabled";
    252     static final String USERAGENT = "useragent";
    253     static final String CLOSEFLAG = "closeOnBack";
    254 
    255     // Container class for the next error dialog that needs to be displayed
    256     private class ErrorDialog {
    257         public final int mTitle;
    258         public final String mDescription;
    259         public final int mError;
    260         ErrorDialog(int title, String desc, int error) {
    261             mTitle = title;
    262             mDescription = desc;
    263             mError = error;
    264         }
    265     }
    266 
    267     private void processNextError() {
    268         if (mQueuedErrors == null) {
    269             return;
    270         }
    271         // The first one is currently displayed so just remove it.
    272         mQueuedErrors.removeFirst();
    273         if (mQueuedErrors.size() == 0) {
    274             mQueuedErrors = null;
    275             return;
    276         }
    277         showError(mQueuedErrors.getFirst());
    278     }
    279 
    280     private DialogInterface.OnDismissListener mDialogListener =
    281             new DialogInterface.OnDismissListener() {
    282                 public void onDismiss(DialogInterface d) {
    283                     processNextError();
    284                 }
    285             };
    286     private LinkedList<ErrorDialog> mQueuedErrors;
    287 
    288     private void queueError(int err, String desc) {
    289         if (mQueuedErrors == null) {
    290             mQueuedErrors = new LinkedList<ErrorDialog>();
    291         }
    292         for (ErrorDialog d : mQueuedErrors) {
    293             if (d.mError == err) {
    294                 // Already saw a similar error, ignore the new one.
    295                 return;
    296             }
    297         }
    298         ErrorDialog errDialog = new ErrorDialog(
    299                 err == WebViewClient.ERROR_FILE_NOT_FOUND ?
    300                 R.string.browserFrameFileErrorLabel :
    301                 R.string.browserFrameNetworkErrorLabel,
    302                 desc, err);
    303         mQueuedErrors.addLast(errDialog);
    304 
    305         // Show the dialog now if the queue was empty and it is in foreground
    306         if (mQueuedErrors.size() == 1 && mInForeground) {
    307             showError(errDialog);
    308         }
    309     }
    310 
    311     private void showError(ErrorDialog errDialog) {
    312         if (mInForeground) {
    313             AlertDialog d = new AlertDialog.Builder(mContext)
    314                     .setTitle(errDialog.mTitle)
    315                     .setMessage(errDialog.mDescription)
    316                     .setPositiveButton(R.string.ok, null)
    317                     .create();
    318             d.setOnDismissListener(mDialogListener);
    319             d.show();
    320         }
    321     }
    322 
    323     // -------------------------------------------------------------------------
    324     // WebViewClient implementation for the main WebView
    325     // -------------------------------------------------------------------------
    326 
    327     private final WebViewClientClassicExt mWebViewClient = new WebViewClientClassicExt() {
    328         private Message mDontResend;
    329         private Message mResend;
    330 
    331         private boolean providersDiffer(String url, String otherUrl) {
    332             Uri uri1 = Uri.parse(url);
    333             Uri uri2 = Uri.parse(otherUrl);
    334             return !uri1.getEncodedAuthority().equals(uri2.getEncodedAuthority());
    335         }
    336 
    337         @Override
    338         public void onPageStarted(WebView view, String url, Bitmap favicon) {
    339             mInPageLoad = true;
    340             mUpdateThumbnail = true;
    341             mPageLoadProgress = INITIAL_PROGRESS;
    342             mCurrentState = new PageState(mContext,
    343                     view.isPrivateBrowsingEnabled(), url, favicon);
    344             mLoadStartTime = SystemClock.uptimeMillis();
    345 
    346             // If we start a touch icon load and then load a new page, we don't
    347             // want to cancel the current touch icon loader. But, we do want to
    348             // create a new one when the touch icon url is known.
    349             if (mTouchIconLoader != null) {
    350                 mTouchIconLoader.mTab = null;
    351                 mTouchIconLoader = null;
    352             }
    353 
    354             // reset the error console
    355             if (mErrorConsole != null) {
    356                 mErrorConsole.clearErrorMessages();
    357                 if (mWebViewController.shouldShowErrorConsole()) {
    358                     mErrorConsole.showConsole(ErrorConsoleView.SHOW_NONE);
    359                 }
    360             }
    361 
    362             // Cancel the auto-login process.
    363             if (mDeviceAccountLogin != null) {
    364                 mDeviceAccountLogin.cancel();
    365                 mDeviceAccountLogin = null;
    366                 mWebViewController.hideAutoLogin(Tab.this);
    367             }
    368 
    369             // finally update the UI in the activity if it is in the foreground
    370             mWebViewController.onPageStarted(Tab.this, view, favicon);
    371 
    372             updateBookmarkedStatus();
    373         }
    374 
    375         @Override
    376         public void onPageFinished(WebView view, String url) {
    377             mDisableOverrideUrlLoading = false;
    378             if (!isPrivateBrowsingEnabled()) {
    379                 LogTag.logPageFinishedLoading(
    380                         url, SystemClock.uptimeMillis() - mLoadStartTime);
    381             }
    382             syncCurrentState(view, url);
    383             mWebViewController.onPageFinished(Tab.this);
    384         }
    385 
    386         // return true if want to hijack the url to let another app to handle it
    387         @Override
    388         public boolean shouldOverrideUrlLoading(WebView view, String url) {
    389             if (!mDisableOverrideUrlLoading && mInForeground) {
    390                 return mWebViewController.shouldOverrideUrlLoading(Tab.this,
    391                         view, url);
    392             } else {
    393                 return false;
    394             }
    395         }
    396 
    397         /**
    398          * Updates the security state. This method is called when we discover
    399          * another resource to be loaded for this page (for example,
    400          * javascript). While we update the security state, we do not update
    401          * the lock icon until we are done loading, as it is slightly more
    402          * secure this way.
    403          */
    404         @Override
    405         public void onLoadResource(WebView view, String url) {
    406             if (url != null && url.length() > 0) {
    407                 // It is only if the page claims to be secure that we may have
    408                 // to update the security state:
    409                 if (mCurrentState.mSecurityState == SecurityState.SECURITY_STATE_SECURE) {
    410                     // If NOT a 'safe' url, change the state to mixed content!
    411                     if (!(URLUtil.isHttpsUrl(url) || URLUtil.isDataUrl(url)
    412                             || URLUtil.isAboutUrl(url))) {
    413                         mCurrentState.mSecurityState = SecurityState.SECURITY_STATE_MIXED;
    414                     }
    415                 }
    416             }
    417         }
    418 
    419         /**
    420          * Show a dialog informing the user of the network error reported by
    421          * WebCore if it is in the foreground.
    422          */
    423         @Override
    424         public void onReceivedError(WebView view, int errorCode,
    425                 String description, String failingUrl) {
    426             if (errorCode != WebViewClient.ERROR_HOST_LOOKUP &&
    427                     errorCode != WebViewClient.ERROR_CONNECT &&
    428                     errorCode != WebViewClient.ERROR_BAD_URL &&
    429                     errorCode != WebViewClient.ERROR_UNSUPPORTED_SCHEME &&
    430                     errorCode != WebViewClient.ERROR_FILE) {
    431                 queueError(errorCode, description);
    432 
    433                 // Don't log URLs when in private browsing mode
    434                 if (!isPrivateBrowsingEnabled()) {
    435                     Log.e(LOGTAG, "onReceivedError " + errorCode + " " + failingUrl
    436                         + " " + description);
    437                 }
    438             }
    439         }
    440 
    441         /**
    442          * Check with the user if it is ok to resend POST data as the page they
    443          * are trying to navigate to is the result of a POST.
    444          */
    445         @Override
    446         public void onFormResubmission(WebView view, final Message dontResend,
    447                                        final Message resend) {
    448             if (!mInForeground) {
    449                 dontResend.sendToTarget();
    450                 return;
    451             }
    452             if (mDontResend != null) {
    453                 Log.w(LOGTAG, "onFormResubmission should not be called again "
    454                         + "while dialog is still up");
    455                 dontResend.sendToTarget();
    456                 return;
    457             }
    458             mDontResend = dontResend;
    459             mResend = resend;
    460             new AlertDialog.Builder(mContext).setTitle(
    461                     R.string.browserFrameFormResubmitLabel).setMessage(
    462                     R.string.browserFrameFormResubmitMessage)
    463                     .setPositiveButton(R.string.ok,
    464                             new DialogInterface.OnClickListener() {
    465                                 public void onClick(DialogInterface dialog,
    466                                         int which) {
    467                                     if (mResend != null) {
    468                                         mResend.sendToTarget();
    469                                         mResend = null;
    470                                         mDontResend = null;
    471                                     }
    472                                 }
    473                             }).setNegativeButton(R.string.cancel,
    474                             new DialogInterface.OnClickListener() {
    475                                 public void onClick(DialogInterface dialog,
    476                                         int which) {
    477                                     if (mDontResend != null) {
    478                                         mDontResend.sendToTarget();
    479                                         mResend = null;
    480                                         mDontResend = null;
    481                                     }
    482                                 }
    483                             }).setOnCancelListener(new OnCancelListener() {
    484                         public void onCancel(DialogInterface dialog) {
    485                             if (mDontResend != null) {
    486                                 mDontResend.sendToTarget();
    487                                 mResend = null;
    488                                 mDontResend = null;
    489                             }
    490                         }
    491                     }).show();
    492         }
    493 
    494         /**
    495          * Insert the url into the visited history database.
    496          * @param url The url to be inserted.
    497          * @param isReload True if this url is being reloaded.
    498          * FIXME: Not sure what to do when reloading the page.
    499          */
    500         @Override
    501         public void doUpdateVisitedHistory(WebView view, String url,
    502                 boolean isReload) {
    503             mWebViewController.doUpdateVisitedHistory(Tab.this, isReload);
    504         }
    505 
    506         /**
    507          * Displays SSL error(s) dialog to the user.
    508          */
    509         @Override
    510         public void onReceivedSslError(final WebView view,
    511                 final SslErrorHandler handler, final SslError error) {
    512             if (!mInForeground) {
    513                 handler.cancel();
    514                 setSecurityState(SecurityState.SECURITY_STATE_NOT_SECURE);
    515                 return;
    516             }
    517             if (mSettings.showSecurityWarnings()) {
    518                 new AlertDialog.Builder(mContext)
    519                     .setTitle(R.string.security_warning)
    520                     .setMessage(R.string.ssl_warnings_header)
    521                     .setIconAttribute(android.R.attr.alertDialogIcon)
    522                     .setPositiveButton(R.string.ssl_continue,
    523                         new DialogInterface.OnClickListener() {
    524                             @Override
    525                             public void onClick(DialogInterface dialog,
    526                                     int whichButton) {
    527                                 handler.proceed();
    528                                 handleProceededAfterSslError(error);
    529                             }
    530                         })
    531                     .setNeutralButton(R.string.view_certificate,
    532                         new DialogInterface.OnClickListener() {
    533                             @Override
    534                             public void onClick(DialogInterface dialog,
    535                                     int whichButton) {
    536                                 mWebViewController.showSslCertificateOnError(
    537                                         view, handler, error);
    538                             }
    539                         })
    540                     .setNegativeButton(R.string.ssl_go_back,
    541                         new DialogInterface.OnClickListener() {
    542                             @Override
    543                             public void onClick(DialogInterface dialog,
    544                                     int whichButton) {
    545                                 dialog.cancel();
    546                             }
    547                         })
    548                     .setOnCancelListener(
    549                         new DialogInterface.OnCancelListener() {
    550                             @Override
    551                             public void onCancel(DialogInterface dialog) {
    552                                 handler.cancel();
    553                                 setSecurityState(SecurityState.SECURITY_STATE_NOT_SECURE);
    554                                 mWebViewController.onUserCanceledSsl(Tab.this);
    555                             }
    556                         })
    557                     .show();
    558             } else {
    559                 handler.proceed();
    560             }
    561         }
    562 
    563         /**
    564          * Called when an SSL error occurred while loading a resource, but the
    565          * WebView but chose to proceed anyway based on a decision retained
    566          * from a previous response to onReceivedSslError(). We update our
    567          * security state to reflect this.
    568          */
    569         @Override
    570         public void onProceededAfterSslError(WebView view, SslError error) {
    571             handleProceededAfterSslError(error);
    572         }
    573 
    574         /**
    575          * Displays client certificate request to the user.
    576          */
    577         @Override
    578         public void onReceivedClientCertRequest(final WebView view,
    579                 final ClientCertRequestHandler handler, final String host_and_port) {
    580             if (!mInForeground) {
    581                 handler.ignore();
    582                 return;
    583             }
    584             int colon = host_and_port.lastIndexOf(':');
    585             String host;
    586             int port;
    587             if (colon == -1) {
    588                 host = host_and_port;
    589                 port = -1;
    590             } else {
    591                 String portString = host_and_port.substring(colon + 1);
    592                 try {
    593                     port = Integer.parseInt(portString);
    594                     host = host_and_port.substring(0, colon);
    595                 } catch  (NumberFormatException e) {
    596                     host = host_and_port;
    597                     port = -1;
    598                 }
    599             }
    600             KeyChain.choosePrivateKeyAlias(
    601                     mWebViewController.getActivity(), new KeyChainAliasCallback() {
    602                 @Override public void alias(String alias) {
    603                     if (alias == null) {
    604                         handler.cancel();
    605                         return;
    606                     }
    607                     new KeyChainLookup(mContext, handler, alias).execute();
    608                 }
    609             }, null, null, host, port, null);
    610         }
    611 
    612         /**
    613          * Handles an HTTP authentication request.
    614          *
    615          * @param handler The authentication handler
    616          * @param host The host
    617          * @param realm The realm
    618          */
    619         @Override
    620         public void onReceivedHttpAuthRequest(WebView view,
    621                 final HttpAuthHandler handler, final String host,
    622                 final String realm) {
    623             mWebViewController.onReceivedHttpAuthRequest(Tab.this, view, handler, host, realm);
    624         }
    625 
    626         @Override
    627         public WebResourceResponse shouldInterceptRequest(WebView view,
    628                 String url) {
    629             WebResourceResponse res = HomeProvider.shouldInterceptRequest(
    630                     mContext, url);
    631             return res;
    632         }
    633 
    634         @Override
    635         public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
    636             if (!mInForeground) {
    637                 return false;
    638             }
    639             return mWebViewController.shouldOverrideKeyEvent(event);
    640         }
    641 
    642         @Override
    643         public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
    644             if (!mInForeground) {
    645                 return;
    646             }
    647             if (!mWebViewController.onUnhandledKeyEvent(event)) {
    648                 super.onUnhandledKeyEvent(view, event);
    649             }
    650         }
    651 
    652         @Override
    653         public void onReceivedLoginRequest(WebView view, String realm,
    654                 String account, String args) {
    655             new DeviceAccountLogin(mWebViewController.getActivity(), view, Tab.this, mWebViewController)
    656                     .handleLogin(realm, account, args);
    657         }
    658 
    659     };
    660 
    661     private void syncCurrentState(WebView view, String url) {
    662         // Sync state (in case of stop/timeout)
    663         mCurrentState.mUrl = view.getUrl();
    664         if (mCurrentState.mUrl == null) {
    665             mCurrentState.mUrl = "";
    666         }
    667         mCurrentState.mOriginalUrl = view.getOriginalUrl();
    668         mCurrentState.mTitle = view.getTitle();
    669         mCurrentState.mFavicon = view.getFavicon();
    670         if (!URLUtil.isHttpsUrl(mCurrentState.mUrl)) {
    671             // In case we stop when loading an HTTPS page from an HTTP page
    672             // but before a provisional load occurred
    673             mCurrentState.mSecurityState = SecurityState.SECURITY_STATE_NOT_SECURE;
    674             mCurrentState.mSslCertificateError = null;
    675         }
    676         mCurrentState.mIncognito = view.isPrivateBrowsingEnabled();
    677     }
    678 
    679     // Called by DeviceAccountLogin when the Tab needs to have the auto-login UI
    680     // displayed.
    681     void setDeviceAccountLogin(DeviceAccountLogin login) {
    682         mDeviceAccountLogin = login;
    683     }
    684 
    685     // Returns non-null if the title bar should display the auto-login UI.
    686     DeviceAccountLogin getDeviceAccountLogin() {
    687         return mDeviceAccountLogin;
    688     }
    689 
    690     // -------------------------------------------------------------------------
    691     // WebChromeClient implementation for the main WebView
    692     // -------------------------------------------------------------------------
    693 
    694     private final WebChromeClient mWebChromeClient = new WebChromeClient() {
    695         // Helper method to create a new tab or sub window.
    696         private void createWindow(final boolean dialog, final Message msg) {
    697             WebView.WebViewTransport transport =
    698                     (WebView.WebViewTransport) msg.obj;
    699             if (dialog) {
    700                 createSubWindow();
    701                 mWebViewController.attachSubWindow(Tab.this);
    702                 transport.setWebView(mSubView);
    703             } else {
    704                 final Tab newTab = mWebViewController.openTab(null,
    705                         Tab.this, true, true);
    706                 transport.setWebView(newTab.getWebView());
    707             }
    708             msg.sendToTarget();
    709         }
    710 
    711         @Override
    712         public boolean onCreateWindow(WebView view, final boolean dialog,
    713                 final boolean userGesture, final Message resultMsg) {
    714             // only allow new window or sub window for the foreground case
    715             if (!mInForeground) {
    716                 return false;
    717             }
    718             // Short-circuit if we can't create any more tabs or sub windows.
    719             if (dialog && mSubView != null) {
    720                 new AlertDialog.Builder(mContext)
    721                         .setTitle(R.string.too_many_subwindows_dialog_title)
    722                         .setIconAttribute(android.R.attr.alertDialogIcon)
    723                         .setMessage(R.string.too_many_subwindows_dialog_message)
    724                         .setPositiveButton(R.string.ok, null)
    725                         .show();
    726                 return false;
    727             } else if (!mWebViewController.getTabControl().canCreateNewTab()) {
    728                 new AlertDialog.Builder(mContext)
    729                         .setTitle(R.string.too_many_windows_dialog_title)
    730                         .setIconAttribute(android.R.attr.alertDialogIcon)
    731                         .setMessage(R.string.too_many_windows_dialog_message)
    732                         .setPositiveButton(R.string.ok, null)
    733                         .show();
    734                 return false;
    735             }
    736 
    737             // Short-circuit if this was a user gesture.
    738             if (userGesture) {
    739                 createWindow(dialog, resultMsg);
    740                 return true;
    741             }
    742 
    743             // Allow the popup and create the appropriate window.
    744             final AlertDialog.OnClickListener allowListener =
    745                     new AlertDialog.OnClickListener() {
    746                         public void onClick(DialogInterface d,
    747                                 int which) {
    748                             createWindow(dialog, resultMsg);
    749                         }
    750                     };
    751 
    752             // Block the popup by returning a null WebView.
    753             final AlertDialog.OnClickListener blockListener =
    754                     new AlertDialog.OnClickListener() {
    755                         public void onClick(DialogInterface d, int which) {
    756                             resultMsg.sendToTarget();
    757                         }
    758                     };
    759 
    760             // Build a confirmation dialog to display to the user.
    761             final AlertDialog d =
    762                     new AlertDialog.Builder(mContext)
    763                     .setIconAttribute(android.R.attr.alertDialogIcon)
    764                     .setMessage(R.string.popup_window_attempt)
    765                     .setPositiveButton(R.string.allow, allowListener)
    766                     .setNegativeButton(R.string.block, blockListener)
    767                     .setCancelable(false)
    768                     .create();
    769 
    770             // Show the confirmation dialog.
    771             d.show();
    772             return true;
    773         }
    774 
    775         @Override
    776         public void onRequestFocus(WebView view) {
    777             if (!mInForeground) {
    778                 mWebViewController.switchToTab(Tab.this);
    779             }
    780         }
    781 
    782         @Override
    783         public void onCloseWindow(WebView window) {
    784             if (mParent != null) {
    785                 // JavaScript can only close popup window.
    786                 if (mInForeground) {
    787                     mWebViewController.switchToTab(mParent);
    788                 }
    789                 mWebViewController.closeTab(Tab.this);
    790             }
    791         }
    792 
    793         @Override
    794         public void onProgressChanged(WebView view, int newProgress) {
    795             mPageLoadProgress = newProgress;
    796             if (newProgress == 100) {
    797                 mInPageLoad = false;
    798             }
    799             mWebViewController.onProgressChanged(Tab.this);
    800             if (mUpdateThumbnail && newProgress == 100) {
    801                 mUpdateThumbnail = false;
    802             }
    803         }
    804 
    805         @Override
    806         public void onReceivedTitle(WebView view, final String title) {
    807             mCurrentState.mTitle = title;
    808             mWebViewController.onReceivedTitle(Tab.this, title);
    809         }
    810 
    811         @Override
    812         public void onReceivedIcon(WebView view, Bitmap icon) {
    813             mCurrentState.mFavicon = icon;
    814             mWebViewController.onFavicon(Tab.this, view, icon);
    815         }
    816 
    817         @Override
    818         public void onReceivedTouchIconUrl(WebView view, String url,
    819                 boolean precomposed) {
    820             final ContentResolver cr = mContext.getContentResolver();
    821             // Let precomposed icons take precedence over non-composed
    822             // icons.
    823             if (precomposed && mTouchIconLoader != null) {
    824                 mTouchIconLoader.cancel(false);
    825                 mTouchIconLoader = null;
    826             }
    827             // Have only one async task at a time.
    828             if (mTouchIconLoader == null) {
    829                 mTouchIconLoader = new DownloadTouchIcon(Tab.this,
    830                         mContext, cr, view);
    831                 mTouchIconLoader.execute(url);
    832             }
    833         }
    834 
    835         @Override
    836         public void onShowCustomView(View view,
    837                 WebChromeClient.CustomViewCallback callback) {
    838             Activity activity = mWebViewController.getActivity();
    839             if (activity != null) {
    840                 onShowCustomView(view, activity.getRequestedOrientation(), callback);
    841             }
    842         }
    843 
    844         @Override
    845         public void onShowCustomView(View view, int requestedOrientation,
    846                 WebChromeClient.CustomViewCallback callback) {
    847             if (mInForeground) mWebViewController.showCustomView(Tab.this, view,
    848                     requestedOrientation, callback);
    849         }
    850 
    851         @Override
    852         public void onHideCustomView() {
    853             if (mInForeground) mWebViewController.hideCustomView();
    854         }
    855 
    856         /**
    857          * The origin has exceeded its database quota.
    858          * @param url the URL that exceeded the quota
    859          * @param databaseIdentifier the identifier of the database on which the
    860          *            transaction that caused the quota overflow was run
    861          * @param currentQuota the current quota for the origin.
    862          * @param estimatedSize the estimated size of the database.
    863          * @param totalUsedQuota is the sum of all origins' quota.
    864          * @param quotaUpdater The callback to run when a decision to allow or
    865          *            deny quota has been made. Don't forget to call this!
    866          */
    867         @Override
    868         public void onExceededDatabaseQuota(String url,
    869             String databaseIdentifier, long currentQuota, long estimatedSize,
    870             long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
    871             mSettings.getWebStorageSizeManager()
    872                     .onExceededDatabaseQuota(url, databaseIdentifier,
    873                             currentQuota, estimatedSize, totalUsedQuota,
    874                             quotaUpdater);
    875         }
    876 
    877         /**
    878          * The Application Cache has exceeded its max size.
    879          * @param spaceNeeded is the amount of disk space that would be needed
    880          *            in order for the last appcache operation to succeed.
    881          * @param totalUsedQuota is the sum of all origins' quota.
    882          * @param quotaUpdater A callback to inform the WebCore thread that a
    883          *            new app cache size is available. This callback must always
    884          *            be executed at some point to ensure that the sleeping
    885          *            WebCore thread is woken up.
    886          */
    887         @Override
    888         public void onReachedMaxAppCacheSize(long spaceNeeded,
    889                 long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
    890             mSettings.getWebStorageSizeManager()
    891                     .onReachedMaxAppCacheSize(spaceNeeded, totalUsedQuota,
    892                             quotaUpdater);
    893         }
    894 
    895         /**
    896          * Instructs the browser to show a prompt to ask the user to set the
    897          * Geolocation permission state for the specified origin.
    898          * @param origin The origin for which Geolocation permissions are
    899          *     requested.
    900          * @param callback The callback to call once the user has set the
    901          *     Geolocation permission state.
    902          */
    903         @Override
    904         public void onGeolocationPermissionsShowPrompt(String origin,
    905                 GeolocationPermissions.Callback callback) {
    906             if (mInForeground) {
    907                 getGeolocationPermissionsPrompt().show(origin, callback);
    908             }
    909         }
    910 
    911         /**
    912          * Instructs the browser to hide the Geolocation permissions prompt.
    913          */
    914         @Override
    915         public void onGeolocationPermissionsHidePrompt() {
    916             if (mInForeground && mGeolocationPermissionsPrompt != null) {
    917                 mGeolocationPermissionsPrompt.hide();
    918             }
    919         }
    920 
    921         /* Adds a JavaScript error message to the system log and if the JS
    922          * console is enabled in the about:debug options, to that console
    923          * also.
    924          * @param consoleMessage the message object.
    925          */
    926         @Override
    927         public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
    928             if (mInForeground) {
    929                 // call getErrorConsole(true) so it will create one if needed
    930                 ErrorConsoleView errorConsole = getErrorConsole(true);
    931                 errorConsole.addErrorMessage(consoleMessage);
    932                 if (mWebViewController.shouldShowErrorConsole()
    933                         && errorConsole.getShowState() !=
    934                             ErrorConsoleView.SHOW_MAXIMIZED) {
    935                     errorConsole.showConsole(ErrorConsoleView.SHOW_MINIMIZED);
    936                 }
    937             }
    938 
    939             // Don't log console messages in private browsing mode
    940             if (isPrivateBrowsingEnabled()) return true;
    941 
    942             String message = "Console: " + consoleMessage.message() + " "
    943                     + consoleMessage.sourceId() +  ":"
    944                     + consoleMessage.lineNumber();
    945 
    946             switch (consoleMessage.messageLevel()) {
    947                 case TIP:
    948                     Log.v(CONSOLE_LOGTAG, message);
    949                     break;
    950                 case LOG:
    951                     Log.i(CONSOLE_LOGTAG, message);
    952                     break;
    953                 case WARNING:
    954                     Log.w(CONSOLE_LOGTAG, message);
    955                     break;
    956                 case ERROR:
    957                     Log.e(CONSOLE_LOGTAG, message);
    958                     break;
    959                 case DEBUG:
    960                     Log.d(CONSOLE_LOGTAG, message);
    961                     break;
    962             }
    963 
    964             return true;
    965         }
    966 
    967         /**
    968          * Ask the browser for an icon to represent a <video> element.
    969          * This icon will be used if the Web page did not specify a poster attribute.
    970          * @return Bitmap The icon or null if no such icon is available.
    971          */
    972         @Override
    973         public Bitmap getDefaultVideoPoster() {
    974             if (mInForeground) {
    975                 return mWebViewController.getDefaultVideoPoster();
    976             }
    977             return null;
    978         }
    979 
    980         /**
    981          * Ask the host application for a custom progress view to show while
    982          * a <video> is loading.
    983          * @return View The progress view.
    984          */
    985         @Override
    986         public View getVideoLoadingProgressView() {
    987             if (mInForeground) {
    988                 return mWebViewController.getVideoLoadingProgressView();
    989             }
    990             return null;
    991         }
    992 
    993         @Override
    994         public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
    995             if (mInForeground) {
    996                 mWebViewController.openFileChooser(uploadMsg, acceptType, capture);
    997             } else {
    998                 uploadMsg.onReceiveValue(null);
    999             }
   1000         }
   1001 
   1002         /**
   1003          * Deliver a list of already-visited URLs
   1004          */
   1005         @Override
   1006         public void getVisitedHistory(final ValueCallback<String[]> callback) {
   1007             mWebViewController.getVisitedHistory(callback);
   1008         }
   1009 
   1010         @Override
   1011         public void setupAutoFill(Message message) {
   1012             // Prompt the user to set up their profile.
   1013             final Message msg = message;
   1014             AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
   1015             LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
   1016                     Context.LAYOUT_INFLATER_SERVICE);
   1017             final View layout = inflater.inflate(R.layout.setup_autofill_dialog, null);
   1018 
   1019             builder.setView(layout)
   1020                 .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
   1021                     @Override
   1022                     public void onClick(DialogInterface dialog, int id) {
   1023                         CheckBox disableAutoFill = (CheckBox) layout.findViewById(
   1024                                 R.id.setup_autofill_dialog_disable_autofill);
   1025 
   1026                         if (disableAutoFill.isChecked()) {
   1027                             // Disable autofill and show a toast with how to turn it on again.
   1028                             mSettings.setAutofillEnabled(false);
   1029                             Toast.makeText(mContext,
   1030                                     R.string.autofill_setup_dialog_negative_toast,
   1031                                     Toast.LENGTH_LONG).show();
   1032                         } else {
   1033                             // Take user to the AutoFill profile editor. When they return,
   1034                             // we will send the message that we pass here which will trigger
   1035                             // the form to get filled out with their new profile.
   1036                             mWebViewController.setupAutoFill(msg);
   1037                         }
   1038                     }
   1039                 })
   1040                 .setNegativeButton(R.string.cancel, null)
   1041                 .show();
   1042         }
   1043     };
   1044 
   1045     // -------------------------------------------------------------------------
   1046     // WebViewClient implementation for the sub window
   1047     // -------------------------------------------------------------------------
   1048 
   1049     // Subclass of WebViewClient used in subwindows to notify the main
   1050     // WebViewClient of certain WebView activities.
   1051     private static class SubWindowClient extends WebViewClientClassicExt {
   1052         // The main WebViewClient.
   1053         private final WebViewClientClassicExt mClient;
   1054         private final WebViewController mController;
   1055 
   1056         SubWindowClient(WebViewClientClassicExt client, WebViewController controller) {
   1057             mClient = client;
   1058             mController = controller;
   1059         }
   1060         @Override
   1061         public void onPageStarted(WebView view, String url, Bitmap favicon) {
   1062             // Unlike the others, do not call mClient's version, which would
   1063             // change the progress bar.  However, we do want to remove the
   1064             // find or select dialog.
   1065             mController.endActionMode();
   1066         }
   1067         @Override
   1068         public void doUpdateVisitedHistory(WebView view, String url,
   1069                 boolean isReload) {
   1070             mClient.doUpdateVisitedHistory(view, url, isReload);
   1071         }
   1072         @Override
   1073         public boolean shouldOverrideUrlLoading(WebView view, String url) {
   1074             return mClient.shouldOverrideUrlLoading(view, url);
   1075         }
   1076         @Override
   1077         public void onReceivedSslError(WebView view, SslErrorHandler handler,
   1078                 SslError error) {
   1079             mClient.onReceivedSslError(view, handler, error);
   1080         }
   1081         @Override
   1082         public void onReceivedClientCertRequest(WebView view,
   1083                 ClientCertRequestHandler handler, String host_and_port) {
   1084             mClient.onReceivedClientCertRequest(view, handler, host_and_port);
   1085         }
   1086         @Override
   1087         public void onReceivedHttpAuthRequest(WebView view,
   1088                 HttpAuthHandler handler, String host, String realm) {
   1089             mClient.onReceivedHttpAuthRequest(view, handler, host, realm);
   1090         }
   1091         @Override
   1092         public void onFormResubmission(WebView view, Message dontResend,
   1093                 Message resend) {
   1094             mClient.onFormResubmission(view, dontResend, resend);
   1095         }
   1096         @Override
   1097         public void onReceivedError(WebView view, int errorCode,
   1098                 String description, String failingUrl) {
   1099             mClient.onReceivedError(view, errorCode, description, failingUrl);
   1100         }
   1101         @Override
   1102         public boolean shouldOverrideKeyEvent(WebView view,
   1103                 android.view.KeyEvent event) {
   1104             return mClient.shouldOverrideKeyEvent(view, event);
   1105         }
   1106         @Override
   1107         public void onUnhandledKeyEvent(WebView view,
   1108                 android.view.KeyEvent event) {
   1109             mClient.onUnhandledKeyEvent(view, event);
   1110         }
   1111     }
   1112 
   1113     // -------------------------------------------------------------------------
   1114     // WebChromeClient implementation for the sub window
   1115     // -------------------------------------------------------------------------
   1116 
   1117     private class SubWindowChromeClient extends WebChromeClient {
   1118         // The main WebChromeClient.
   1119         private final WebChromeClient mClient;
   1120 
   1121         SubWindowChromeClient(WebChromeClient client) {
   1122             mClient = client;
   1123         }
   1124         @Override
   1125         public void onProgressChanged(WebView view, int newProgress) {
   1126             mClient.onProgressChanged(view, newProgress);
   1127         }
   1128         @Override
   1129         public boolean onCreateWindow(WebView view, boolean dialog,
   1130                 boolean userGesture, android.os.Message resultMsg) {
   1131             return mClient.onCreateWindow(view, dialog, userGesture, resultMsg);
   1132         }
   1133         @Override
   1134         public void onCloseWindow(WebView window) {
   1135             if (window != mSubView) {
   1136                 Log.e(LOGTAG, "Can't close the window");
   1137             }
   1138             mWebViewController.dismissSubWindow(Tab.this);
   1139         }
   1140     }
   1141 
   1142     // -------------------------------------------------------------------------
   1143 
   1144     // Construct a new tab
   1145     Tab(WebViewController wvcontroller, WebView w) {
   1146         this(wvcontroller, w, null);
   1147     }
   1148 
   1149     Tab(WebViewController wvcontroller, Bundle state) {
   1150         this(wvcontroller, null, state);
   1151     }
   1152 
   1153     Tab(WebViewController wvcontroller, WebView w, Bundle state) {
   1154         mWebViewController = wvcontroller;
   1155         mContext = mWebViewController.getContext();
   1156         mSettings = BrowserSettings.getInstance();
   1157         mDataController = DataController.getInstance(mContext);
   1158         mCurrentState = new PageState(mContext, w != null
   1159                 ? w.isPrivateBrowsingEnabled() : false);
   1160         mInPageLoad = false;
   1161         mInForeground = false;
   1162 
   1163         mDownloadListener = new BrowserDownloadListener() {
   1164             public void onDownloadStart(String url, String userAgent,
   1165                     String contentDisposition, String mimetype, String referer,
   1166                     long contentLength) {
   1167                 mWebViewController.onDownloadStart(Tab.this, url, userAgent, contentDisposition,
   1168                         mimetype, referer, contentLength);
   1169             }
   1170         };
   1171         mWebBackForwardListClient = new WebBackForwardListClient() {
   1172             @Override
   1173             public void onNewHistoryItem(WebHistoryItem item) {
   1174                 if (mClearHistoryUrlPattern != null) {
   1175                     boolean match =
   1176                         mClearHistoryUrlPattern.matcher(item.getOriginalUrl()).matches();
   1177                     if (LOGD_ENABLED) {
   1178                         Log.d(LOGTAG, "onNewHistoryItem: match=" + match + "\n\t"
   1179                                 + item.getUrl() + "\n\t"
   1180                                 + mClearHistoryUrlPattern);
   1181                     }
   1182                     if (match) {
   1183                         if (mMainView != null) {
   1184                             mMainView.clearHistory();
   1185                         }
   1186                     }
   1187                     mClearHistoryUrlPattern = null;
   1188                 }
   1189             }
   1190         };
   1191 
   1192         mCaptureWidth = mContext.getResources().getDimensionPixelSize(
   1193                 R.dimen.tab_thumbnail_width);
   1194         mCaptureHeight = mContext.getResources().getDimensionPixelSize(
   1195                 R.dimen.tab_thumbnail_height);
   1196         updateShouldCaptureThumbnails();
   1197         restoreState(state);
   1198         if (getId() == -1) {
   1199             mId = TabControl.getNextId();
   1200         }
   1201         setWebView(w);
   1202         mHandler = new Handler() {
   1203             @Override
   1204             public void handleMessage(Message m) {
   1205                 switch (m.what) {
   1206                 case MSG_CAPTURE:
   1207                     capture();
   1208                     break;
   1209                 }
   1210             }
   1211         };
   1212     }
   1213 
   1214     public boolean shouldUpdateThumbnail() {
   1215         return mUpdateThumbnail;
   1216     }
   1217 
   1218     /**
   1219      * This is used to get a new ID when the tab has been preloaded, before it is displayed and
   1220      * added to TabControl. Preloaded tabs can be created before restoreInstanceState, leading
   1221      * to overlapping IDs between the preloaded and restored tabs.
   1222      */
   1223     public void refreshIdAfterPreload() {
   1224         mId = TabControl.getNextId();
   1225     }
   1226 
   1227     public void updateShouldCaptureThumbnails() {
   1228         if (mWebViewController.shouldCaptureThumbnails()) {
   1229             synchronized (Tab.this) {
   1230                 if (mCapture == null) {
   1231                     mCapture = Bitmap.createBitmap(mCaptureWidth, mCaptureHeight,
   1232                             Bitmap.Config.RGB_565);
   1233                     mCapture.eraseColor(Color.WHITE);
   1234                     if (mInForeground) {
   1235                         postCapture();
   1236                     }
   1237                 }
   1238             }
   1239         } else {
   1240             synchronized (Tab.this) {
   1241                 mCapture = null;
   1242                 deleteThumbnail();
   1243             }
   1244         }
   1245     }
   1246 
   1247     public void setController(WebViewController ctl) {
   1248         mWebViewController = ctl;
   1249         updateShouldCaptureThumbnails();
   1250     }
   1251 
   1252     public long getId() {
   1253         return mId;
   1254     }
   1255 
   1256     void setWebView(WebView w) {
   1257         setWebView(w, true);
   1258     }
   1259 
   1260     /**
   1261      * Sets the WebView for this tab, correctly removing the old WebView from
   1262      * the container view.
   1263      */
   1264     void setWebView(WebView w, boolean restore) {
   1265         if (mMainView == w) {
   1266             return;
   1267         }
   1268 
   1269         // If the WebView is changing, the page will be reloaded, so any ongoing
   1270         // Geolocation permission requests are void.
   1271         if (mGeolocationPermissionsPrompt != null) {
   1272             mGeolocationPermissionsPrompt.hide();
   1273         }
   1274 
   1275         mWebViewController.onSetWebView(this, w);
   1276 
   1277         if (mMainView != null) {
   1278             mMainView.setPictureListener(null);
   1279             if (w != null) {
   1280                 syncCurrentState(w, null);
   1281             } else {
   1282                 mCurrentState = new PageState(mContext, false);
   1283             }
   1284         }
   1285         // set the new one
   1286         mMainView = w;
   1287         // attach the WebViewClient, WebChromeClient and DownloadListener
   1288         if (mMainView != null) {
   1289             mMainView.setWebViewClient(mWebViewClient);
   1290             mMainView.setWebChromeClient(mWebChromeClient);
   1291             // Attach DownloadManager so that downloads can start in an active
   1292             // or a non-active window. This can happen when going to a site that
   1293             // does a redirect after a period of time. The user could have
   1294             // switched to another tab while waiting for the download to start.
   1295             mMainView.setDownloadListener(mDownloadListener);
   1296             if (BrowserWebView.isClassic()) {
   1297                 getWebViewClassic().setWebBackForwardListClient(mWebBackForwardListClient);
   1298             }
   1299             TabControl tc = mWebViewController.getTabControl();
   1300             if (tc != null && tc.getOnThumbnailUpdatedListener() != null) {
   1301                 mMainView.setPictureListener(this);
   1302             }
   1303             if (restore && (mSavedState != null)) {
   1304                 restoreUserAgent();
   1305                 WebBackForwardList restoredState
   1306                         = mMainView.restoreState(mSavedState);
   1307                 if (restoredState == null || restoredState.getSize() == 0) {
   1308                     Log.w(LOGTAG, "Failed to restore WebView state!");
   1309                     loadUrl(mCurrentState.mOriginalUrl, null);
   1310                 }
   1311                 mSavedState = null;
   1312             }
   1313         }
   1314     }
   1315 
   1316     /**
   1317      * Destroy the tab's main WebView and subWindow if any
   1318      */
   1319     void destroy() {
   1320         if (mMainView != null) {
   1321             dismissSubWindow();
   1322             // save the WebView to call destroy() after detach it from the tab
   1323             WebView webView = mMainView;
   1324             setWebView(null);
   1325             webView.destroy();
   1326         }
   1327     }
   1328 
   1329     /**
   1330      * Remove the tab from the parent
   1331      */
   1332     void removeFromTree() {
   1333         // detach the children
   1334         if (mChildren != null) {
   1335             for(Tab t : mChildren) {
   1336                 t.setParent(null);
   1337             }
   1338         }
   1339         // remove itself from the parent list
   1340         if (mParent != null) {
   1341             mParent.mChildren.remove(this);
   1342         }
   1343         deleteThumbnail();
   1344     }
   1345 
   1346     /**
   1347      * Create a new subwindow unless a subwindow already exists.
   1348      * @return True if a new subwindow was created. False if one already exists.
   1349      */
   1350     boolean createSubWindow() {
   1351         if (mSubView == null) {
   1352             mWebViewController.createSubWindow(this);
   1353             mSubView.setWebViewClient(new SubWindowClient(mWebViewClient,
   1354                     mWebViewController));
   1355             mSubView.setWebChromeClient(new SubWindowChromeClient(
   1356                     mWebChromeClient));
   1357             // Set a different DownloadListener for the mSubView, since it will
   1358             // just need to dismiss the mSubView, rather than close the Tab
   1359             mSubView.setDownloadListener(new BrowserDownloadListener() {
   1360                 public void onDownloadStart(String url, String userAgent,
   1361                         String contentDisposition, String mimetype, String referer,
   1362                         long contentLength) {
   1363                     mWebViewController.onDownloadStart(Tab.this, url, userAgent,
   1364                             contentDisposition, mimetype, referer, contentLength);
   1365                     if (mSubView.copyBackForwardList().getSize() == 0) {
   1366                         // This subwindow was opened for the sole purpose of
   1367                         // downloading a file. Remove it.
   1368                         mWebViewController.dismissSubWindow(Tab.this);
   1369                     }
   1370                 }
   1371             });
   1372             mSubView.setOnCreateContextMenuListener(mWebViewController.getActivity());
   1373             return true;
   1374         }
   1375         return false;
   1376     }
   1377 
   1378     /**
   1379      * Dismiss the subWindow for the tab.
   1380      */
   1381     void dismissSubWindow() {
   1382         if (mSubView != null) {
   1383             mWebViewController.endActionMode();
   1384             mSubView.destroy();
   1385             mSubView = null;
   1386             mSubViewContainer = null;
   1387         }
   1388     }
   1389 
   1390 
   1391     /**
   1392      * Set the parent tab of this tab.
   1393      */
   1394     void setParent(Tab parent) {
   1395         if (parent == this) {
   1396             throw new IllegalStateException("Cannot set parent to self!");
   1397         }
   1398         mParent = parent;
   1399         // This tab may have been freed due to low memory. If that is the case,
   1400         // the parent tab id is already saved. If we are changing that id
   1401         // (most likely due to removing the parent tab) we must update the
   1402         // parent tab id in the saved Bundle.
   1403         if (mSavedState != null) {
   1404             if (parent == null) {
   1405                 mSavedState.remove(PARENTTAB);
   1406             } else {
   1407                 mSavedState.putLong(PARENTTAB, parent.getId());
   1408             }
   1409         }
   1410 
   1411         // Sync the WebView useragent with the parent
   1412         if (parent != null && mSettings.hasDesktopUseragent(parent.getWebView())
   1413                 != mSettings.hasDesktopUseragent(getWebView())) {
   1414             mSettings.toggleDesktopUseragent(getWebView());
   1415         }
   1416 
   1417         if (parent != null && parent.getId() == getId()) {
   1418             throw new IllegalStateException("Parent has same ID as child!");
   1419         }
   1420     }
   1421 
   1422     /**
   1423      * If this Tab was created through another Tab, then this method returns
   1424      * that Tab.
   1425      * @return the Tab parent or null
   1426      */
   1427     public Tab getParent() {
   1428         return mParent;
   1429     }
   1430 
   1431     /**
   1432      * When a Tab is created through the content of another Tab, then we
   1433      * associate the Tabs.
   1434      * @param child the Tab that was created from this Tab
   1435      */
   1436     void addChildTab(Tab child) {
   1437         if (mChildren == null) {
   1438             mChildren = new Vector<Tab>();
   1439         }
   1440         mChildren.add(child);
   1441         child.setParent(this);
   1442     }
   1443 
   1444     Vector<Tab> getChildren() {
   1445         return mChildren;
   1446     }
   1447 
   1448     void resume() {
   1449         if (mMainView != null) {
   1450             setupHwAcceleration(mMainView);
   1451             mMainView.onResume();
   1452             if (mSubView != null) {
   1453                 mSubView.onResume();
   1454             }
   1455         }
   1456     }
   1457 
   1458     private void setupHwAcceleration(View web) {
   1459         if (web == null) return;
   1460         BrowserSettings settings = BrowserSettings.getInstance();
   1461         if (settings.isHardwareAccelerated()) {
   1462             web.setLayerType(View.LAYER_TYPE_NONE, null);
   1463         } else {
   1464             web.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
   1465         }
   1466     }
   1467 
   1468     void pause() {
   1469         if (mMainView != null) {
   1470             mMainView.onPause();
   1471             if (mSubView != null) {
   1472                 mSubView.onPause();
   1473             }
   1474         }
   1475     }
   1476 
   1477     void putInForeground() {
   1478         if (mInForeground) {
   1479             return;
   1480         }
   1481         mInForeground = true;
   1482         resume();
   1483         Activity activity = mWebViewController.getActivity();
   1484         mMainView.setOnCreateContextMenuListener(activity);
   1485         if (mSubView != null) {
   1486             mSubView.setOnCreateContextMenuListener(activity);
   1487         }
   1488         // Show the pending error dialog if the queue is not empty
   1489         if (mQueuedErrors != null && mQueuedErrors.size() >  0) {
   1490             showError(mQueuedErrors.getFirst());
   1491         }
   1492         mWebViewController.bookmarkedStatusHasChanged(this);
   1493     }
   1494 
   1495     void putInBackground() {
   1496         if (!mInForeground) {
   1497             return;
   1498         }
   1499         capture();
   1500         mInForeground = false;
   1501         pause();
   1502         mMainView.setOnCreateContextMenuListener(null);
   1503         if (mSubView != null) {
   1504             mSubView.setOnCreateContextMenuListener(null);
   1505         }
   1506     }
   1507 
   1508     boolean inForeground() {
   1509         return mInForeground;
   1510     }
   1511 
   1512     /**
   1513      * Return the top window of this tab; either the subwindow if it is not
   1514      * null or the main window.
   1515      * @return The top window of this tab.
   1516      */
   1517     WebView getTopWindow() {
   1518         if (mSubView != null) {
   1519             return mSubView;
   1520         }
   1521         return mMainView;
   1522     }
   1523 
   1524     /**
   1525      * Return the main window of this tab. Note: if a tab is freed in the
   1526      * background, this can return null. It is only guaranteed to be
   1527      * non-null for the current tab.
   1528      * @return The main WebView of this tab.
   1529      */
   1530     WebView getWebView() {
   1531         return mMainView;
   1532     }
   1533 
   1534     /**
   1535      * Return the underlying WebViewClassic implementation. As with getWebView,
   1536      * this maybe null for background tabs.
   1537      * @return The main WebView of this tab.
   1538      */
   1539     WebViewClassic getWebViewClassic() {
   1540         if (!BrowserWebView.isClassic()) {
   1541             return null;
   1542         }
   1543         return WebViewClassic.fromWebView(mMainView);
   1544     }
   1545 
   1546     void setViewContainer(View container) {
   1547         mContainer = container;
   1548     }
   1549 
   1550     View getViewContainer() {
   1551         return mContainer;
   1552     }
   1553 
   1554     /**
   1555      * Return whether private browsing is enabled for the main window of
   1556      * this tab.
   1557      * @return True if private browsing is enabled.
   1558      */
   1559     boolean isPrivateBrowsingEnabled() {
   1560         return mCurrentState.mIncognito;
   1561     }
   1562 
   1563     /**
   1564      * Return the subwindow of this tab or null if there is no subwindow.
   1565      * @return The subwindow of this tab or null.
   1566      */
   1567     WebView getSubWebView() {
   1568         return mSubView;
   1569     }
   1570 
   1571     void setSubWebView(WebView subView) {
   1572         mSubView = subView;
   1573     }
   1574 
   1575     View getSubViewContainer() {
   1576         return mSubViewContainer;
   1577     }
   1578 
   1579     void setSubViewContainer(View subViewContainer) {
   1580         mSubViewContainer = subViewContainer;
   1581     }
   1582 
   1583     /**
   1584      * @return The geolocation permissions prompt for this tab.
   1585      */
   1586     GeolocationPermissionsPrompt getGeolocationPermissionsPrompt() {
   1587         if (mGeolocationPermissionsPrompt == null) {
   1588             ViewStub stub = (ViewStub) mContainer
   1589                     .findViewById(R.id.geolocation_permissions_prompt);
   1590             mGeolocationPermissionsPrompt = (GeolocationPermissionsPrompt) stub
   1591                     .inflate();
   1592         }
   1593         return mGeolocationPermissionsPrompt;
   1594     }
   1595 
   1596     /**
   1597      * @return The application id string
   1598      */
   1599     String getAppId() {
   1600         return mAppId;
   1601     }
   1602 
   1603     /**
   1604      * Set the application id string
   1605      * @param id
   1606      */
   1607     void setAppId(String id) {
   1608         mAppId = id;
   1609     }
   1610 
   1611     boolean closeOnBack() {
   1612         return mCloseOnBack;
   1613     }
   1614 
   1615     void setCloseOnBack(boolean close) {
   1616         mCloseOnBack = close;
   1617     }
   1618 
   1619     String getUrl() {
   1620         return UrlUtils.filteredUrl(mCurrentState.mUrl);
   1621     }
   1622 
   1623     String getOriginalUrl() {
   1624         if (mCurrentState.mOriginalUrl == null) {
   1625             return getUrl();
   1626         }
   1627         return UrlUtils.filteredUrl(mCurrentState.mOriginalUrl);
   1628     }
   1629 
   1630     /**
   1631      * Get the title of this tab.
   1632      */
   1633     String getTitle() {
   1634         if (mCurrentState.mTitle == null && mInPageLoad) {
   1635             return mContext.getString(R.string.title_bar_loading);
   1636         }
   1637         return mCurrentState.mTitle;
   1638     }
   1639 
   1640     /**
   1641      * Get the favicon of this tab.
   1642      */
   1643     Bitmap getFavicon() {
   1644         if (mCurrentState.mFavicon != null) {
   1645             return mCurrentState.mFavicon;
   1646         }
   1647         return getDefaultFavicon(mContext);
   1648     }
   1649 
   1650     public boolean isBookmarkedSite() {
   1651         return mCurrentState.mIsBookmarkedSite;
   1652     }
   1653 
   1654     /**
   1655      * Return the tab's error console. Creates the console if createIfNEcessary
   1656      * is true and we haven't already created the console.
   1657      * @param createIfNecessary Flag to indicate if the console should be
   1658      *            created if it has not been already.
   1659      * @return The tab's error console, or null if one has not been created and
   1660      *         createIfNecessary is false.
   1661      */
   1662     ErrorConsoleView getErrorConsole(boolean createIfNecessary) {
   1663         if (createIfNecessary && mErrorConsole == null) {
   1664             mErrorConsole = new ErrorConsoleView(mContext);
   1665             mErrorConsole.setWebView(mMainView);
   1666         }
   1667         return mErrorConsole;
   1668     }
   1669 
   1670     /**
   1671      * Sets the security state, clears the SSL certificate error and informs
   1672      * the controller.
   1673      */
   1674     private void setSecurityState(SecurityState securityState) {
   1675         mCurrentState.mSecurityState = securityState;
   1676         mCurrentState.mSslCertificateError = null;
   1677         mWebViewController.onUpdatedSecurityState(this);
   1678     }
   1679 
   1680     /**
   1681      * @return The tab's security state.
   1682      */
   1683     SecurityState getSecurityState() {
   1684         return mCurrentState.mSecurityState;
   1685     }
   1686 
   1687     /**
   1688      * Gets the SSL certificate error, if any, for the page's main resource.
   1689      * This is only non-null when the security state is
   1690      * SECURITY_STATE_BAD_CERTIFICATE.
   1691      */
   1692     SslError getSslCertificateError() {
   1693         return mCurrentState.mSslCertificateError;
   1694     }
   1695 
   1696     int getLoadProgress() {
   1697         if (mInPageLoad) {
   1698             return mPageLoadProgress;
   1699         }
   1700         return 100;
   1701     }
   1702 
   1703     /**
   1704      * @return TRUE if onPageStarted is called while onPageFinished is not
   1705      *         called yet.
   1706      */
   1707     boolean inPageLoad() {
   1708         return mInPageLoad;
   1709     }
   1710 
   1711     /**
   1712      * @return The Bundle with the tab's state if it can be saved, otherwise null
   1713      */
   1714     public Bundle saveState() {
   1715         // If the WebView is null it means we ran low on memory and we already
   1716         // stored the saved state in mSavedState.
   1717         if (mMainView == null) {
   1718             return mSavedState;
   1719         }
   1720 
   1721         if (TextUtils.isEmpty(mCurrentState.mUrl)) {
   1722             return null;
   1723         }
   1724 
   1725         mSavedState = new Bundle();
   1726         WebBackForwardList savedList = mMainView.saveState(mSavedState);
   1727         if (savedList == null || savedList.getSize() == 0) {
   1728             Log.w(LOGTAG, "Failed to save back/forward list for "
   1729                     + mCurrentState.mUrl);
   1730         }
   1731 
   1732         mSavedState.putLong(ID, mId);
   1733         mSavedState.putString(CURRURL, mCurrentState.mUrl);
   1734         mSavedState.putString(CURRTITLE, mCurrentState.mTitle);
   1735         mSavedState.putBoolean(INCOGNITO, mMainView.isPrivateBrowsingEnabled());
   1736         if (mAppId != null) {
   1737             mSavedState.putString(APPID, mAppId);
   1738         }
   1739         mSavedState.putBoolean(CLOSEFLAG, mCloseOnBack);
   1740         // Remember the parent tab so the relationship can be restored.
   1741         if (mParent != null) {
   1742             mSavedState.putLong(PARENTTAB, mParent.mId);
   1743         }
   1744         mSavedState.putBoolean(USERAGENT,
   1745                 mSettings.hasDesktopUseragent(getWebView()));
   1746         return mSavedState;
   1747     }
   1748 
   1749     /*
   1750      * Restore the state of the tab.
   1751      */
   1752     private void restoreState(Bundle b) {
   1753         mSavedState = b;
   1754         if (mSavedState == null) {
   1755             return;
   1756         }
   1757         // Restore the internal state even if the WebView fails to restore.
   1758         // This will maintain the app id, original url and close-on-exit values.
   1759         mId = b.getLong(ID);
   1760         mAppId = b.getString(APPID);
   1761         mCloseOnBack = b.getBoolean(CLOSEFLAG);
   1762         restoreUserAgent();
   1763         String url = b.getString(CURRURL);
   1764         String title = b.getString(CURRTITLE);
   1765         boolean incognito = b.getBoolean(INCOGNITO);
   1766         mCurrentState = new PageState(mContext, incognito, url, null);
   1767         mCurrentState.mTitle = title;
   1768         synchronized (Tab.this) {
   1769             if (mCapture != null) {
   1770                 DataController.getInstance(mContext).loadThumbnail(this);
   1771             }
   1772         }
   1773     }
   1774 
   1775     private void restoreUserAgent() {
   1776         if (mMainView == null || mSavedState == null) {
   1777             return;
   1778         }
   1779         if (mSavedState.getBoolean(USERAGENT)
   1780                 != mSettings.hasDesktopUseragent(mMainView)) {
   1781             mSettings.toggleDesktopUseragent(mMainView);
   1782         }
   1783     }
   1784 
   1785     public void updateBookmarkedStatus() {
   1786         mDataController.queryBookmarkStatus(getUrl(), mIsBookmarkCallback);
   1787     }
   1788 
   1789     private DataController.OnQueryUrlIsBookmark mIsBookmarkCallback
   1790             = new DataController.OnQueryUrlIsBookmark() {
   1791         @Override
   1792         public void onQueryUrlIsBookmark(String url, boolean isBookmark) {
   1793             if (mCurrentState.mUrl.equals(url)) {
   1794                 mCurrentState.mIsBookmarkedSite = isBookmark;
   1795                 mWebViewController.bookmarkedStatusHasChanged(Tab.this);
   1796             }
   1797         }
   1798     };
   1799 
   1800     public Bitmap getScreenshot() {
   1801         synchronized (Tab.this) {
   1802             return mCapture;
   1803         }
   1804     }
   1805 
   1806     public boolean isSnapshot() {
   1807         return false;
   1808     }
   1809 
   1810     private static class SaveCallback implements ValueCallback<Boolean> {
   1811         boolean mResult;
   1812 
   1813         @Override
   1814         public void onReceiveValue(Boolean value) {
   1815             mResult = value;
   1816             synchronized (this) {
   1817                 notifyAll();
   1818             }
   1819         }
   1820 
   1821     }
   1822 
   1823     /**
   1824      * Must be called on the UI thread
   1825      */
   1826     public ContentValues createSnapshotValues() {
   1827         WebViewClassic web = getWebViewClassic();
   1828         if (web == null) return null;
   1829         ContentValues values = new ContentValues();
   1830         values.put(Snapshots.TITLE, mCurrentState.mTitle);
   1831         values.put(Snapshots.URL, mCurrentState.mUrl);
   1832         values.put(Snapshots.BACKGROUND, web.getPageBackgroundColor());
   1833         values.put(Snapshots.DATE_CREATED, System.currentTimeMillis());
   1834         values.put(Snapshots.FAVICON, compressBitmap(getFavicon()));
   1835         Bitmap screenshot = Controller.createScreenshot(mMainView,
   1836                 Controller.getDesiredThumbnailWidth(mContext),
   1837                 Controller.getDesiredThumbnailHeight(mContext));
   1838         values.put(Snapshots.THUMBNAIL, compressBitmap(screenshot));
   1839         return values;
   1840     }
   1841 
   1842     /**
   1843      * Probably want to call this on a background thread
   1844      */
   1845     public boolean saveViewState(ContentValues values) {
   1846         WebViewClassic web = getWebViewClassic();
   1847         if (web == null) return false;
   1848         String path = UUID.randomUUID().toString();
   1849         SaveCallback callback = new SaveCallback();
   1850         OutputStream outs = null;
   1851         try {
   1852             outs = mContext.openFileOutput(path, Context.MODE_PRIVATE);
   1853             GZIPOutputStream stream = new GZIPOutputStream(outs);
   1854             synchronized (callback) {
   1855                 web.saveViewState(stream, callback);
   1856                 callback.wait();
   1857             }
   1858             stream.flush();
   1859             stream.close();
   1860         } catch (Exception e) {
   1861             Log.w(LOGTAG, "Failed to save view state", e);
   1862             if (outs != null) {
   1863                 try {
   1864                     outs.close();
   1865                 } catch (IOException ignore) {}
   1866             }
   1867             File file = mContext.getFileStreamPath(path);
   1868             if (file.exists() && !file.delete()) {
   1869                 file.deleteOnExit();
   1870             }
   1871             return false;
   1872         }
   1873         File savedFile = mContext.getFileStreamPath(path);
   1874         if (!callback.mResult) {
   1875             if (!savedFile.delete()) {
   1876                 savedFile.deleteOnExit();
   1877             }
   1878             return false;
   1879         }
   1880         long size = savedFile.length();
   1881         values.put(Snapshots.VIEWSTATE_PATH, path);
   1882         values.put(Snapshots.VIEWSTATE_SIZE, size);
   1883         return true;
   1884     }
   1885 
   1886     public byte[] compressBitmap(Bitmap bitmap) {
   1887         if (bitmap == null) {
   1888             return null;
   1889         }
   1890         ByteArrayOutputStream stream = new ByteArrayOutputStream();
   1891         bitmap.compress(CompressFormat.PNG, 100, stream);
   1892         return stream.toByteArray();
   1893     }
   1894 
   1895     public void loadUrl(String url, Map<String, String> headers) {
   1896         if (mMainView != null) {
   1897             mPageLoadProgress = INITIAL_PROGRESS;
   1898             mInPageLoad = true;
   1899             mCurrentState = new PageState(mContext, false, url, null);
   1900             mWebViewController.onPageStarted(this, mMainView, null);
   1901             mMainView.loadUrl(url, headers);
   1902         }
   1903     }
   1904 
   1905     public void disableUrlOverridingForLoad() {
   1906         mDisableOverrideUrlLoading = true;
   1907     }
   1908 
   1909     protected void capture() {
   1910         if (mMainView == null || mCapture == null) return;
   1911         if (mMainView.getContentWidth() <= 0 || mMainView.getContentHeight() <= 0) {
   1912             return;
   1913         }
   1914         Canvas c = new Canvas(mCapture);
   1915         final int left = mMainView.getScrollX();
   1916         final int top = mMainView.getScrollY() + mMainView.getVisibleTitleHeight();
   1917         int state = c.save();
   1918         c.translate(-left, -top);
   1919         float scale = mCaptureWidth / (float) mMainView.getWidth();
   1920         c.scale(scale, scale, left, top);
   1921         if (mMainView instanceof BrowserWebView) {
   1922             ((BrowserWebView)mMainView).drawContent(c);
   1923         } else {
   1924             mMainView.draw(c);
   1925         }
   1926         c.restoreToCount(state);
   1927         // manually anti-alias the edges for the tilt
   1928         c.drawRect(0, 0, 1, mCapture.getHeight(), sAlphaPaint);
   1929         c.drawRect(mCapture.getWidth() - 1, 0, mCapture.getWidth(),
   1930                 mCapture.getHeight(), sAlphaPaint);
   1931         c.drawRect(0, 0, mCapture.getWidth(), 1, sAlphaPaint);
   1932         c.drawRect(0, mCapture.getHeight() - 1, mCapture.getWidth(),
   1933                 mCapture.getHeight(), sAlphaPaint);
   1934         c.setBitmap(null);
   1935         mHandler.removeMessages(MSG_CAPTURE);
   1936         persistThumbnail();
   1937         TabControl tc = mWebViewController.getTabControl();
   1938         if (tc != null) {
   1939             OnThumbnailUpdatedListener updateListener
   1940                     = tc.getOnThumbnailUpdatedListener();
   1941             if (updateListener != null) {
   1942                 updateListener.onThumbnailUpdated(this);
   1943             }
   1944         }
   1945     }
   1946 
   1947     @Override
   1948     public void onNewPicture(WebView view, Picture picture) {
   1949         postCapture();
   1950     }
   1951 
   1952     private void postCapture() {
   1953         if (!mHandler.hasMessages(MSG_CAPTURE)) {
   1954             mHandler.sendEmptyMessageDelayed(MSG_CAPTURE, CAPTURE_DELAY);
   1955         }
   1956     }
   1957 
   1958     public boolean canGoBack() {
   1959         return mMainView != null ? mMainView.canGoBack() : false;
   1960     }
   1961 
   1962     public boolean canGoForward() {
   1963         return mMainView != null ? mMainView.canGoForward() : false;
   1964     }
   1965 
   1966     public void goBack() {
   1967         if (mMainView != null) {
   1968             mMainView.goBack();
   1969         }
   1970     }
   1971 
   1972     public void goForward() {
   1973         if (mMainView != null) {
   1974             mMainView.goForward();
   1975         }
   1976     }
   1977 
   1978     /**
   1979      * Causes the tab back/forward stack to be cleared once, if the given URL is the next URL
   1980      * to be added to the stack.
   1981      *
   1982      * This is used to ensure that preloaded URLs that are not subsequently seen by the user do
   1983      * not appear in the back stack.
   1984      */
   1985     public void clearBackStackWhenItemAdded(Pattern urlPattern) {
   1986         mClearHistoryUrlPattern = urlPattern;
   1987     }
   1988 
   1989     protected void persistThumbnail() {
   1990         DataController.getInstance(mContext).saveThumbnail(this);
   1991     }
   1992 
   1993     protected void deleteThumbnail() {
   1994         DataController.getInstance(mContext).deleteThumbnail(this);
   1995     }
   1996 
   1997     void updateCaptureFromBlob(byte[] blob) {
   1998         synchronized (Tab.this) {
   1999             if (mCapture == null) {
   2000                 return;
   2001             }
   2002             ByteBuffer buffer = ByteBuffer.wrap(blob);
   2003             try {
   2004                 mCapture.copyPixelsFromBuffer(buffer);
   2005             } catch (RuntimeException rex) {
   2006                 Log.e(LOGTAG, "Load capture has mismatched sizes; buffer: "
   2007                         + buffer.capacity() + " blob: " + blob.length
   2008                         + "capture: " + mCapture.getByteCount());
   2009                 throw rex;
   2010             }
   2011         }
   2012     }
   2013 
   2014     @Override
   2015     public String toString() {
   2016         StringBuilder builder = new StringBuilder(100);
   2017         builder.append(mId);
   2018         builder.append(") has parent: ");
   2019         if (getParent() != null) {
   2020             builder.append("true[");
   2021             builder.append(getParent().getId());
   2022             builder.append("]");
   2023         } else {
   2024             builder.append("false");
   2025         }
   2026         builder.append(", incog: ");
   2027         builder.append(isPrivateBrowsingEnabled());
   2028         if (!isPrivateBrowsingEnabled()) {
   2029             builder.append(", title: ");
   2030             builder.append(getTitle());
   2031             builder.append(", url: ");
   2032             builder.append(getUrl());
   2033         }
   2034         return builder.toString();
   2035     }
   2036 
   2037     private void handleProceededAfterSslError(SslError error) {
   2038         if (error.getUrl().equals(mCurrentState.mUrl)) {
   2039             // The security state should currently be SECURITY_STATE_SECURE.
   2040             setSecurityState(SecurityState.SECURITY_STATE_BAD_CERTIFICATE);
   2041             mCurrentState.mSslCertificateError = error;
   2042         } else if (getSecurityState() == SecurityState.SECURITY_STATE_SECURE) {
   2043             // The page's main resource is secure and this error is for a
   2044             // sub-resource.
   2045             setSecurityState(SecurityState.SECURITY_STATE_MIXED);
   2046         }
   2047     }
   2048 }
   2049