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