1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.chrome.browser; 6 7 import android.app.Activity; 8 import android.content.Context; 9 import android.graphics.Bitmap; 10 import android.graphics.Color; 11 import android.view.ContextMenu; 12 import android.view.View; 13 14 import org.chromium.base.CalledByNative; 15 import org.chromium.base.ObserverList; 16 import org.chromium.base.TraceEvent; 17 import org.chromium.base.VisibleForTesting; 18 import org.chromium.chrome.browser.banners.AppBannerManager; 19 import org.chromium.chrome.browser.contextmenu.ChromeContextMenuItemDelegate; 20 import org.chromium.chrome.browser.contextmenu.ChromeContextMenuPopulator; 21 import org.chromium.chrome.browser.contextmenu.ContextMenuParams; 22 import org.chromium.chrome.browser.contextmenu.ContextMenuPopulator; 23 import org.chromium.chrome.browser.contextmenu.ContextMenuPopulatorWrapper; 24 import org.chromium.chrome.browser.contextmenu.EmptyChromeContextMenuItemDelegate; 25 import org.chromium.chrome.browser.dom_distiller.DomDistillerFeedbackReporter; 26 import org.chromium.chrome.browser.infobar.AutoLoginProcessor; 27 import org.chromium.chrome.browser.infobar.InfoBarContainer; 28 import org.chromium.chrome.browser.profiles.Profile; 29 import org.chromium.chrome.browser.ui.toolbar.ToolbarModelSecurityLevel; 30 import org.chromium.content.browser.ContentView; 31 import org.chromium.content.browser.ContentViewClient; 32 import org.chromium.content.browser.ContentViewCore; 33 import org.chromium.content.browser.NavigationClient; 34 import org.chromium.content.browser.WebContentsObserverAndroid; 35 import org.chromium.content_public.browser.LoadUrlParams; 36 import org.chromium.content_public.browser.NavigationHistory; 37 import org.chromium.content_public.browser.WebContents; 38 import org.chromium.ui.base.Clipboard; 39 import org.chromium.ui.base.WindowAndroid; 40 41 import java.util.concurrent.atomic.AtomicInteger; 42 43 /** 44 * The basic Java representation of a tab. Contains and manages a {@link ContentView}. 45 * 46 * Tab provides common functionality for ChromeShell Tab as well as Chrome on Android's 47 * tab. It is intended to be extended either on Java or both Java and C++, with ownership managed 48 * by this base class. 49 * 50 * Extending just Java: 51 * - Just extend the class normally. Do not override initializeNative(). 52 * Extending Java and C++: 53 * - Because of the inner-workings of JNI, the subclass is responsible for constructing the native 54 * subclass, which in turn constructs TabAndroid (the native counterpart to Tab), which in 55 * turn sets the native pointer for Tab. For destruction, subclasses in Java must clear 56 * their own native pointer reference, but Tab#destroy() will handle deleting the native 57 * object. 58 * 59 * Notes on {@link Tab#getId()}: 60 * 61 * Tabs are all generated using a static {@link AtomicInteger} which means they are unique across 62 * all {@link Activity}s running in the same {@link android.app.Application} process. Calling 63 * {@link Tab#incrementIdCounterTo(int)} will ensure new {@link Tab}s get ids greater than or equal 64 * to the parameter passed to that method. This should be used when doing things like loading 65 * persisted {@link Tab}s from disk on process start to ensure all new {@link Tab}s don't have id 66 * collision. 67 * Some {@link Activity}s will not call this because they do not persist state, which means those 68 * ids can potentially conflict with the ones restored from persisted state depending on which 69 * {@link Activity} runs first on process start. If {@link Tab}s are ever shared across 70 * {@link Activity}s or mixed with {@link Tab}s from other {@link Activity}s conflicts can occur 71 * unless special care is taken to make sure {@link Tab#incrementIdCounterTo(int)} is called with 72 * the correct value across all affected {@link Activity}s. 73 */ 74 public class Tab implements NavigationClient { 75 public static final int INVALID_TAB_ID = -1; 76 77 /** Used for automatically generating tab ids. */ 78 private static final AtomicInteger sIdCounter = new AtomicInteger(); 79 80 private long mNativeTabAndroid; 81 82 /** Unique id of this tab (within its container). */ 83 private final int mId; 84 85 /** Whether or not this tab is an incognito tab. */ 86 private final boolean mIncognito; 87 88 /** An Application {@link Context}. Unlike {@link #mContext}, this is the only one that is 89 * publicly exposed to help prevent leaking the {@link Activity}. */ 90 private final Context mApplicationContext; 91 92 /** The {@link Context} used to create {@link View}s and other Android components. Unlike 93 * {@link #mApplicationContext}, this is not publicly exposed to help prevent leaking the 94 * {@link Activity}. */ 95 private final Context mContext; 96 97 /** Gives {@link Tab} a way to interact with the Android window. */ 98 private final WindowAndroid mWindowAndroid; 99 100 /** The current native page (e.g. chrome-native://newtab), or {@code null} if there is none. */ 101 private NativePage mNativePage; 102 103 /** InfoBar container to show InfoBars for this tab. */ 104 private InfoBarContainer mInfoBarContainer; 105 106 /** Manages app banners shown for this tab. */ 107 private AppBannerManager mAppBannerManager; 108 109 /** The sync id of the Tab if session sync is enabled. */ 110 private int mSyncId; 111 112 /** 113 * The {@link ContentViewCore} showing the current page or {@code null} if the tab is frozen. 114 */ 115 private ContentViewCore mContentViewCore; 116 117 /** 118 * A list of Tab observers. These are used to broadcast Tab events to listeners. 119 */ 120 private final ObserverList<TabObserver> mObservers = new ObserverList<TabObserver>(); 121 122 // Content layer Observers and Delegates 123 private ContentViewClient mContentViewClient; 124 private WebContentsObserverAndroid mWebContentsObserver; 125 private VoiceSearchTabHelper mVoiceSearchTabHelper; 126 private TabChromeWebContentsDelegateAndroid mWebContentsDelegate; 127 private DomDistillerFeedbackReporter mDomDistillerFeedbackReporter; 128 129 /** 130 * If this tab was opened from another tab, store the id of the tab that 131 * caused it to be opened so that we can activate it when this tab gets 132 * closed. 133 */ 134 private int mParentId = INVALID_TAB_ID; 135 136 /** 137 * Whether the tab should be grouped with its parent tab. 138 */ 139 private boolean mGroupedWithParent = true; 140 141 private boolean mIsClosing = false; 142 143 private Bitmap mFavicon = null; 144 145 private String mFaviconUrl = null; 146 147 /** 148 * A default {@link ChromeContextMenuItemDelegate} that supports some of the context menu 149 * functionality. 150 */ 151 protected class TabChromeContextMenuItemDelegate 152 extends EmptyChromeContextMenuItemDelegate { 153 private final Clipboard mClipboard; 154 155 /** 156 * Builds a {@link TabChromeContextMenuItemDelegate} instance. 157 */ 158 public TabChromeContextMenuItemDelegate() { 159 mClipboard = new Clipboard(getApplicationContext()); 160 } 161 162 @Override 163 public boolean isIncognito() { 164 return mIncognito; 165 } 166 167 @Override 168 public void onSaveToClipboard(String text, boolean isUrl) { 169 mClipboard.setText(text, text); 170 } 171 172 @Override 173 public void onSaveImageToClipboard(String url) { 174 mClipboard.setHTMLText("<img src=\"" + url + "\">", url, url); 175 } 176 177 @Override 178 public String getPageUrl() { 179 return getUrl(); 180 } 181 } 182 183 /** 184 * A basic {@link ChromeWebContentsDelegateAndroid} that forwards some calls to the registered 185 * {@link TabObserver}s. Meant to be overridden by subclasses. 186 */ 187 public class TabChromeWebContentsDelegateAndroid 188 extends ChromeWebContentsDelegateAndroid { 189 @Override 190 public void onLoadProgressChanged(int progress) { 191 for (TabObserver observer : mObservers) { 192 observer.onLoadProgressChanged(Tab.this, progress); 193 } 194 } 195 196 @Override 197 public void onLoadStarted() { 198 for (TabObserver observer : mObservers) observer.onLoadStarted(Tab.this); 199 } 200 201 @Override 202 public void onLoadStopped() { 203 for (TabObserver observer : mObservers) observer.onLoadStopped(Tab.this); 204 } 205 206 @Override 207 public void onUpdateUrl(String url) { 208 for (TabObserver observer : mObservers) observer.onUpdateUrl(Tab.this, url); 209 } 210 211 @Override 212 public void showRepostFormWarningDialog(final ContentViewCore contentViewCore) { 213 RepostFormWarningDialog warningDialog = new RepostFormWarningDialog( 214 new Runnable() { 215 @Override 216 public void run() { 217 getWebContents().getNavigationController().cancelPendingReload(); 218 } 219 }, new Runnable() { 220 @Override 221 public void run() { 222 getWebContents().getNavigationController().continuePendingReload(); 223 } 224 }); 225 Activity activity = (Activity) mContext; 226 warningDialog.show(activity.getFragmentManager(), null); 227 } 228 229 @Override 230 public void toggleFullscreenModeForTab(boolean enableFullscreen) { 231 for (TabObserver observer : mObservers) { 232 observer.onToggleFullscreenMode(Tab.this, enableFullscreen); 233 } 234 } 235 236 @Override 237 public void navigationStateChanged(int flags) { 238 if ((flags & INVALIDATE_TYPE_TITLE) != 0) { 239 for (TabObserver observer : mObservers) observer.onTitleUpdated(Tab.this); 240 } 241 if ((flags & INVALIDATE_TYPE_URL) != 0) { 242 for (TabObserver observer : mObservers) observer.onUrlUpdated(Tab.this); 243 } 244 } 245 246 @Override 247 public void visibleSSLStateChanged() { 248 for (TabObserver observer : mObservers) observer.onSSLStateUpdated(Tab.this); 249 } 250 } 251 252 private class TabContextMenuPopulator extends ContextMenuPopulatorWrapper { 253 public TabContextMenuPopulator(ContextMenuPopulator populator) { 254 super(populator); 255 } 256 257 @Override 258 public void buildContextMenu(ContextMenu menu, Context context, ContextMenuParams params) { 259 super.buildContextMenu(menu, context, params); 260 for (TabObserver observer : mObservers) observer.onContextMenuShown(Tab.this, menu); 261 } 262 } 263 264 private class TabWebContentsObserverAndroid extends WebContentsObserverAndroid { 265 public TabWebContentsObserverAndroid(WebContents webContents) { 266 super(webContents); 267 } 268 269 @Override 270 public void navigationEntryCommitted() { 271 if (getNativePage() != null) { 272 pushNativePageStateToNavigationEntry(); 273 } 274 } 275 276 @Override 277 public void didFailLoad(boolean isProvisionalLoad, boolean isMainFrame, int errorCode, 278 String description, String failingUrl) { 279 for (TabObserver observer : mObservers) { 280 observer.onDidFailLoad(Tab.this, isProvisionalLoad, isMainFrame, errorCode, 281 description, failingUrl); 282 } 283 } 284 285 @Override 286 public void didStartProvisionalLoadForFrame(long frameId, long parentFrameId, 287 boolean isMainFrame, String validatedUrl, boolean isErrorPage, 288 boolean isIframeSrcdoc) { 289 for (TabObserver observer : mObservers) { 290 observer.onDidStartProvisionalLoadForFrame(Tab.this, frameId, parentFrameId, 291 isMainFrame, validatedUrl, isErrorPage, isIframeSrcdoc); 292 } 293 } 294 295 @Override 296 public void didNavigateMainFrame(String url, String baseUrl, 297 boolean isNavigationToDifferentPage, boolean isFragmentNavigation, int statusCode) { 298 for (TabObserver observer : mObservers) { 299 observer.onDidNavigateMainFrame( 300 Tab.this, url, baseUrl, isNavigationToDifferentPage, 301 isFragmentNavigation, statusCode); 302 303 } 304 } 305 306 @Override 307 public void didChangeThemeColor(int color) { 308 for (TabObserver observer : mObservers) { 309 observer.onDidChangeThemeColor(color); 310 } 311 } 312 } 313 314 /** 315 * Creates an instance of a {@link Tab} with no id. 316 * @param incognito Whether or not this tab is incognito. 317 * @param context An instance of a {@link Context}. 318 * @param window An instance of a {@link WindowAndroid}. 319 */ 320 @VisibleForTesting 321 public Tab(boolean incognito, Context context, WindowAndroid window) { 322 this(INVALID_TAB_ID, incognito, context, window); 323 } 324 325 /** 326 * Creates an instance of a {@link Tab}. 327 * @param id The id this tab should be identified with. 328 * @param incognito Whether or not this tab is incognito. 329 * @param context An instance of a {@link Context}. 330 * @param window An instance of a {@link WindowAndroid}. 331 */ 332 public Tab(int id, boolean incognito, Context context, WindowAndroid window) { 333 this(INVALID_TAB_ID, id, incognito, context, window); 334 } 335 336 /** 337 * Creates an instance of a {@link Tab}. 338 * @param id The id this tab should be identified with. 339 * @param parentId The id id of the tab that caused this tab to be opened. 340 * @param incognito Whether or not this tab is incognito. 341 * @param context An instance of a {@link Context}. 342 * @param window An instance of a {@link WindowAndroid}. 343 */ 344 public Tab(int id, int parentId, boolean incognito, Context context, WindowAndroid window) { 345 // We need a valid Activity Context to build the ContentView with. 346 assert context == null || context instanceof Activity; 347 348 mId = generateValidId(id); 349 mParentId = parentId; 350 mIncognito = incognito; 351 // TODO(dtrainor): Only store application context here. 352 mContext = context; 353 mApplicationContext = context != null ? context.getApplicationContext() : null; 354 mWindowAndroid = window; 355 } 356 357 /** 358 * Adds a {@link TabObserver} to be notified on {@link Tab} changes. 359 * @param observer The {@link TabObserver} to add. 360 */ 361 public void addObserver(TabObserver observer) { 362 mObservers.addObserver(observer); 363 } 364 365 /** 366 * Removes a {@link TabObserver}. 367 * @param observer The {@link TabObserver} to remove. 368 */ 369 public void removeObserver(TabObserver observer) { 370 mObservers.removeObserver(observer); 371 } 372 373 /** 374 * @return Whether or not this tab has a previous navigation entry. 375 */ 376 public boolean canGoBack() { 377 return getWebContents() != null && getWebContents().getNavigationController().canGoBack(); 378 } 379 380 /** 381 * @return Whether or not this tab has a navigation entry after the current one. 382 */ 383 public boolean canGoForward() { 384 return getWebContents() != null && getWebContents().getNavigationController() 385 .canGoForward(); 386 } 387 388 /** 389 * Goes to the navigation entry before the current one. 390 */ 391 public void goBack() { 392 if (getWebContents() != null) getWebContents().getNavigationController().goBack(); 393 } 394 395 /** 396 * Goes to the navigation entry after the current one. 397 */ 398 public void goForward() { 399 if (getWebContents() != null) getWebContents().getNavigationController().goForward(); 400 } 401 402 @Override 403 public NavigationHistory getDirectedNavigationHistory(boolean isForward, int itemLimit) { 404 if (getWebContents() != null) { 405 return getWebContents().getNavigationController() 406 .getDirectedNavigationHistory(isForward, itemLimit); 407 } else { 408 return new NavigationHistory(); 409 } 410 } 411 412 @Override 413 public void goToNavigationIndex(int index) { 414 if (getWebContents() != null) { 415 getWebContents().getNavigationController().goToNavigationIndex(index); 416 } 417 } 418 419 /** 420 * Loads the current navigation if there is a pending lazy load (after tab restore). 421 */ 422 public void loadIfNecessary() { 423 if (getWebContents() != null) getWebContents().getNavigationController().loadIfNecessary(); 424 } 425 426 /** 427 * Requests the current navigation to be loaded upon the next call to loadIfNecessary(). 428 */ 429 protected void requestRestoreLoad() { 430 if (getWebContents() != null) { 431 getWebContents().getNavigationController().requestRestoreLoad(); 432 } 433 } 434 435 /** 436 * Causes this tab to navigate to the specified URL. 437 * @param params parameters describing the url load. Note that it is important to set correct 438 * page transition as it is used for ranking URLs in the history so the omnibox 439 * can report suggestions correctly. 440 * @return FULL_PRERENDERED_PAGE_LOAD or PARTIAL_PRERENDERED_PAGE_LOAD if the page has been 441 * prerendered. DEFAULT_PAGE_LOAD if it had not. 442 */ 443 public int loadUrl(LoadUrlParams params) { 444 TraceEvent.begin(); 445 446 // We load the URL from the tab rather than directly from the ContentView so the tab has a 447 // chance of using a prerenderer page is any. 448 int loadType = nativeLoadUrl( 449 mNativeTabAndroid, 450 params.getUrl(), 451 params.getVerbatimHeaders(), 452 params.getPostData(), 453 params.getTransitionType(), 454 params.getReferrer() != null ? params.getReferrer().getUrl() : null, 455 // Policy will be ignored for null referrer url, 0 is just a placeholder. 456 // TODO(ppi): Should we pass Referrer jobject and add JNI methods to read it from 457 // the native? 458 params.getReferrer() != null ? params.getReferrer().getPolicy() : 0, 459 params.getIsRendererInitiated()); 460 461 TraceEvent.end(); 462 463 for (TabObserver observer : mObservers) { 464 observer.onLoadUrl(this, params.getUrl(), loadType); 465 } 466 return loadType; 467 } 468 469 /** 470 * @return Whether or not the {@link Tab} is currently showing an interstitial page, such as 471 * a bad HTTPS page. 472 */ 473 public boolean isShowingInterstitialPage() { 474 return getWebContents() != null && getWebContents().isShowingInterstitialPage(); 475 } 476 477 /** 478 * @return Whether or not the tab has something valid to render. 479 */ 480 public boolean isReady() { 481 return mNativePage != null || (mContentViewCore != null && mContentViewCore.isReady()); 482 } 483 484 /** 485 * @return The {@link View} displaying the current page in the tab. This might be a 486 * native view or a placeholder view for content rendered by the compositor. 487 * This can be {@code null}, if the tab is frozen or being initialized or destroyed. 488 */ 489 public View getView() { 490 return mNativePage != null ? mNativePage.getView() : 491 (mContentViewCore != null ? mContentViewCore.getContainerView() : null); 492 } 493 494 /** 495 * @return The width of the content of this tab. Can be 0 if there is no content. 496 */ 497 public int getWidth() { 498 View view = getView(); 499 return view != null ? view.getWidth() : 0; 500 } 501 502 /** 503 * @return The height of the content of this tab. Can be 0 if there is no content. 504 */ 505 public int getHeight() { 506 View view = getView(); 507 return view != null ? view.getHeight() : 0; 508 } 509 510 /** 511 * @return The application {@link Context} associated with this tab. 512 */ 513 protected Context getApplicationContext() { 514 return mApplicationContext; 515 } 516 517 /** 518 * @return The infobar container. 519 */ 520 public final InfoBarContainer getInfoBarContainer() { 521 return mInfoBarContainer; 522 } 523 524 /** 525 * Create an {@code AutoLoginProcessor} to decide how to handle login 526 * requests. 527 */ 528 protected AutoLoginProcessor createAutoLoginProcessor() { 529 return new AutoLoginProcessor() { 530 @Override 531 public void processAutoLoginResult(String accountName, String authToken, 532 boolean success, String result) { 533 } 534 }; 535 } 536 537 /** 538 * Prints the current page. 539 * 540 * @return Whether the printing process is started successfully. 541 **/ 542 public boolean print() { 543 assert mNativeTabAndroid != 0; 544 return nativePrint(mNativeTabAndroid); 545 } 546 547 /** 548 * Reloads the current page content. 549 */ 550 public void reload() { 551 // TODO(dtrainor): Should we try to rebuild the ContentView if it's frozen? 552 if (getWebContents() != null) getWebContents().getNavigationController().reload(true); 553 } 554 555 /** 556 * Reloads the current page content. 557 * This version ignores the cache and reloads from the network. 558 */ 559 public void reloadIgnoringCache() { 560 if (getWebContents() != null) { 561 getWebContents().getNavigationController().reloadIgnoringCache(true); 562 } 563 } 564 565 /** Stop the current navigation. */ 566 public void stopLoading() { 567 if (getWebContents() != null) getWebContents().stop(); 568 } 569 570 /** 571 * @return The background color of the tab. 572 */ 573 public int getBackgroundColor() { 574 if (mNativePage != null) return mNativePage.getBackgroundColor(); 575 if (getWebContents() != null) return getWebContents().getBackgroundColor(); 576 return Color.WHITE; 577 } 578 579 /** 580 * @return The web contents associated with this tab. 581 */ 582 public WebContents getWebContents() { 583 return mContentViewCore != null ? mContentViewCore.getWebContents() : null; 584 } 585 586 /** 587 * @return The profile associated with this tab. 588 */ 589 public Profile getProfile() { 590 if (mNativeTabAndroid == 0) return null; 591 return nativeGetProfileAndroid(mNativeTabAndroid); 592 } 593 594 /** 595 * For more information about the uniqueness of {@link #getId()} see comments on {@link Tab}. 596 * @see Tab 597 * @return The id representing this tab. 598 */ 599 @CalledByNative 600 public int getId() { 601 return mId; 602 } 603 604 /** 605 * @return Whether or not this tab is incognito. 606 */ 607 public boolean isIncognito() { 608 return mIncognito; 609 } 610 611 /** 612 * @return The {@link ContentViewCore} associated with the current page, or {@code null} if 613 * there is no current page or the current page is displayed using a native view. 614 */ 615 public ContentViewCore getContentViewCore() { 616 return mNativePage == null ? mContentViewCore : null; 617 } 618 619 /** 620 * @return The {@link NativePage} associated with the current page, or {@code null} if there is 621 * no current page or the current page is displayed using something besides 622 * {@link NativePage}. 623 */ 624 public NativePage getNativePage() { 625 return mNativePage; 626 } 627 628 /** 629 * @return Whether or not the {@link Tab} represents a {@link NativePage}. 630 */ 631 public boolean isNativePage() { 632 return mNativePage != null; 633 } 634 635 /** 636 * Set whether or not the {@link ContentViewCore} should be using a desktop user agent for the 637 * currently loaded page. 638 * @param useDesktop If {@code true}, use a desktop user agent. Otherwise use a mobile one. 639 * @param reloadOnChange Reload the page if the user agent has changed. 640 */ 641 public void setUseDesktopUserAgent(boolean useDesktop, boolean reloadOnChange) { 642 if (getWebContents() != null) { 643 getWebContents().getNavigationController() 644 .setUseDesktopUserAgent(useDesktop, reloadOnChange); 645 } 646 } 647 648 /** 649 * @return Whether or not the {@link ContentViewCore} is using a desktop user agent. 650 */ 651 public boolean getUseDesktopUserAgent() { 652 return getWebContents() != null && getWebContents().getNavigationController() 653 .getUseDesktopUserAgent(); 654 } 655 656 /** 657 * @return The current {ToolbarModelSecurityLevel} for the tab. 658 */ 659 public int getSecurityLevel() { 660 if (mNativeTabAndroid == 0) return ToolbarModelSecurityLevel.NONE; 661 return nativeGetSecurityLevel(mNativeTabAndroid); 662 } 663 664 /** 665 * @return The sync id of the tab if session sync is enabled, {@code 0} otherwise. 666 */ 667 @CalledByNative 668 protected int getSyncId() { 669 return mSyncId; 670 } 671 672 /** 673 * @param syncId The sync id of the tab if session sync is enabled. 674 */ 675 @CalledByNative 676 protected void setSyncId(int syncId) { 677 mSyncId = syncId; 678 } 679 680 /** 681 * @return An {@link ObserverList.RewindableIterator} instance that points to all of 682 * the current {@link TabObserver}s on this class. Note that calling 683 * {@link java.util.Iterator#remove()} will throw an 684 * {@link UnsupportedOperationException}. 685 */ 686 protected ObserverList.RewindableIterator<TabObserver> getTabObservers() { 687 return mObservers.rewindableIterator(); 688 } 689 690 /** 691 * @return The {@link ContentViewClient} currently bound to any {@link ContentViewCore} 692 * associated with the current page. There can still be a {@link ContentViewClient} 693 * even when there is no {@link ContentViewCore}. 694 */ 695 protected ContentViewClient getContentViewClient() { 696 return mContentViewClient; 697 } 698 699 /** 700 * @param client The {@link ContentViewClient} to be bound to any current or new 701 * {@link ContentViewCore}s associated with this {@link Tab}. 702 */ 703 protected void setContentViewClient(ContentViewClient client) { 704 if (mContentViewClient == client) return; 705 706 ContentViewClient oldClient = mContentViewClient; 707 mContentViewClient = client; 708 709 if (mContentViewCore == null) return; 710 711 if (mContentViewClient != null) { 712 mContentViewCore.setContentViewClient(mContentViewClient); 713 } else if (oldClient != null) { 714 // We can't set a null client, but we should clear references to the last one. 715 mContentViewCore.setContentViewClient(new ContentViewClient()); 716 } 717 } 718 719 /** 720 * Triggers the showing logic for the view backing this tab. 721 */ 722 protected void show() { 723 if (mContentViewCore != null) mContentViewCore.onShow(); 724 } 725 726 /** 727 * Triggers the hiding logic for the view backing the tab. 728 */ 729 protected void hide() { 730 if (mContentViewCore != null) mContentViewCore.onHide(); 731 } 732 733 /** 734 * Shows the given {@code nativePage} if it's not already showing. 735 * @param nativePage The {@link NativePage} to show. 736 */ 737 protected void showNativePage(NativePage nativePage) { 738 if (mNativePage == nativePage) return; 739 NativePage previousNativePage = mNativePage; 740 mNativePage = nativePage; 741 pushNativePageStateToNavigationEntry(); 742 for (TabObserver observer : mObservers) observer.onContentChanged(this); 743 destroyNativePageInternal(previousNativePage); 744 } 745 746 /** 747 * Replaces the current NativePage with a empty stand-in for a NativePage. This can be used 748 * to reduce memory pressure. 749 */ 750 public void freezeNativePage() { 751 if (mNativePage == null || mNativePage instanceof FrozenNativePage) return; 752 assert mNativePage.getView().getParent() == null : "Cannot freeze visible native page"; 753 mNativePage = FrozenNativePage.freeze(mNativePage); 754 } 755 756 /** 757 * Hides the current {@link NativePage}, if any, and shows the {@link ContentViewCore}'s view. 758 */ 759 protected void showRenderedPage() { 760 if (mNativePage == null) return; 761 NativePage previousNativePage = mNativePage; 762 mNativePage = null; 763 for (TabObserver observer : mObservers) observer.onContentChanged(this); 764 destroyNativePageInternal(previousNativePage); 765 } 766 767 /** 768 * Initializes this {@link Tab}. 769 */ 770 public void initialize() { 771 initializeNative(); 772 } 773 774 /** 775 * Builds the native counterpart to this class. Meant to be overridden by subclasses to build 776 * subclass native counterparts instead. Subclasses should not call this via super and instead 777 * rely on the native class to create the JNI association. 778 */ 779 protected void initializeNative() { 780 if (mNativeTabAndroid == 0) nativeInit(); 781 assert mNativeTabAndroid != 0; 782 } 783 784 /** 785 * A helper method to initialize a {@link ContentViewCore} without any 786 * native WebContents pointer. 787 */ 788 protected final void initContentViewCore() { 789 initContentViewCore(ContentViewUtil.createNativeWebContents(mIncognito)); 790 } 791 792 /** 793 * Creates and initializes the {@link ContentViewCore}. 794 * 795 * @param nativeWebContents The native web contents pointer. 796 */ 797 protected void initContentViewCore(long nativeWebContents) { 798 ContentViewCore cvc = new ContentViewCore(mContext); 799 ContentView cv = ContentView.newInstance(mContext, cvc); 800 cvc.initialize(cv, cv, nativeWebContents, getWindowAndroid()); 801 setContentViewCore(cvc); 802 } 803 804 /** 805 * Completes the {@link ContentViewCore} specific initialization around a native WebContents 806 * pointer. {@link #getNativePage()} will still return the {@link NativePage} if there is one. 807 * All initialization that needs to reoccur after a web contents swap should be added here. 808 * <p /> 809 * NOTE: If you attempt to pass a native WebContents that does not have the same incognito 810 * state as this tab this call will fail. 811 * 812 * @param cvc The content view core that needs to be set as active view for the tab. 813 */ 814 protected void setContentViewCore(ContentViewCore cvc) { 815 NativePage previousNativePage = mNativePage; 816 mNativePage = null; 817 destroyNativePageInternal(previousNativePage); 818 819 mContentViewCore = cvc; 820 821 mWebContentsDelegate = createWebContentsDelegate(); 822 mWebContentsObserver = new TabWebContentsObserverAndroid(mContentViewCore.getWebContents()); 823 mVoiceSearchTabHelper = new VoiceSearchTabHelper(mContentViewCore.getWebContents()); 824 825 if (mContentViewClient != null) mContentViewCore.setContentViewClient(mContentViewClient); 826 827 assert mNativeTabAndroid != 0; 828 nativeInitWebContents( 829 mNativeTabAndroid, mIncognito, mContentViewCore, mWebContentsDelegate, 830 new TabContextMenuPopulator(createContextMenuPopulator())); 831 832 // In the case where restoring a Tab or showing a prerendered one we already have a 833 // valid infobar container, no need to recreate one. 834 if (mInfoBarContainer == null) { 835 // The InfoBarContainer needs to be created after the ContentView has been natively 836 // initialized. 837 WebContents webContents = mContentViewCore.getWebContents(); 838 mInfoBarContainer = new InfoBarContainer( 839 (Activity) mContext, createAutoLoginProcessor(), getId(), 840 mContentViewCore.getContainerView(), webContents); 841 } else { 842 mInfoBarContainer.onParentViewChanged(getId(), mContentViewCore.getContainerView()); 843 } 844 845 if (AppBannerManager.isEnabled() && mAppBannerManager == null) { 846 mAppBannerManager = new AppBannerManager(this); 847 } 848 849 if (DomDistillerFeedbackReporter.isEnabled() && mDomDistillerFeedbackReporter == null) { 850 mDomDistillerFeedbackReporter = new DomDistillerFeedbackReporter(this); 851 } 852 853 for (TabObserver observer : mObservers) observer.onContentChanged(this); 854 855 // For browser tabs, we want to set accessibility focus to the page 856 // when it loads. This is not the default behavior for embedded 857 // web views. 858 mContentViewCore.setShouldSetAccessibilityFocusOnPageLoad(true); 859 } 860 861 /** 862 * Cleans up all internal state, destroying any {@link NativePage} or {@link ContentViewCore} 863 * currently associated with this {@link Tab}. This also destroys the native counterpart 864 * to this class, which means that all subclasses should erase their native pointers after 865 * this method is called. Once this call is made this {@link Tab} should no longer be used. 866 */ 867 public void destroy() { 868 for (TabObserver observer : mObservers) observer.onDestroyed(this); 869 mObservers.clear(); 870 871 NativePage currentNativePage = mNativePage; 872 mNativePage = null; 873 destroyNativePageInternal(currentNativePage); 874 destroyContentViewCore(true); 875 876 // Destroys the native tab after destroying the ContentView but before destroying the 877 // InfoBarContainer. The native tab should be destroyed before the infobar container as 878 // destroying the native tab cleanups up any remaining infobars. The infobar container 879 // expects all infobars to be cleaned up before its own destruction. 880 assert mNativeTabAndroid != 0; 881 nativeDestroy(mNativeTabAndroid); 882 assert mNativeTabAndroid == 0; 883 884 if (mInfoBarContainer != null) { 885 mInfoBarContainer.destroy(); 886 mInfoBarContainer = null; 887 } 888 } 889 890 /** 891 * @return Whether or not this Tab has a live native component. 892 */ 893 public boolean isInitialized() { 894 return mNativeTabAndroid != 0; 895 } 896 897 /** 898 * @return The url associated with the tab. 899 */ 900 @CalledByNative 901 public String getUrl() { 902 return getWebContents() != null ? getWebContents().getUrl() : ""; 903 } 904 905 /** 906 * @return The tab title. 907 */ 908 @CalledByNative 909 public String getTitle() { 910 if (mNativePage != null) return mNativePage.getTitle(); 911 if (getWebContents() != null) return getWebContents().getTitle(); 912 return ""; 913 } 914 915 /** 916 * @return The bitmap of the favicon scaled to 16x16dp. null if no favicon 917 * is specified or it requires the default favicon. 918 */ 919 public Bitmap getFavicon() { 920 String url = getUrl(); 921 // Invalidate our cached values if necessary. 922 if (url == null || !url.equals(mFaviconUrl)) { 923 mFavicon = null; 924 mFaviconUrl = null; 925 } 926 927 if (mFavicon == null) { 928 // If we have no content return null. 929 if (getNativePage() == null && getContentViewCore() == null) return null; 930 931 Bitmap favicon = nativeGetFavicon(mNativeTabAndroid); 932 933 // If the favicon is not yet valid (i.e. it's either blank or a placeholder), then do 934 // not cache the results. We still return this though so we have something to show. 935 if (favicon != null && nativeIsFaviconValid(mNativeTabAndroid)) { 936 mFavicon = favicon; 937 mFaviconUrl = url; 938 } 939 940 return favicon; 941 } 942 943 return mFavicon; 944 } 945 946 /** 947 * Loads the tab if it's not loaded (e.g. because it was killed in background). 948 * @return true iff the Tab handled the request. 949 */ 950 @CalledByNative 951 public boolean loadIfNeeded() { 952 return false; 953 } 954 955 /** 956 * @return Whether or not the tab is in the closing process. 957 */ 958 public boolean isClosing() { 959 return mIsClosing; 960 } 961 962 /** 963 * @param closing Whether or not the tab is in the closing process. 964 */ 965 public void setClosing(boolean closing) { 966 mIsClosing = closing; 967 } 968 969 /** 970 * @return The id of the tab that caused this tab to be opened. 971 */ 972 public int getParentId() { 973 return mParentId; 974 } 975 976 /** 977 * @return Whether the tab should be grouped with its parent tab (true by default). 978 */ 979 public boolean isGroupedWithParent() { 980 return mGroupedWithParent; 981 } 982 983 /** 984 * Sets whether the tab should be grouped with its parent tab. 985 * 986 * @param groupedWithParent The new value. 987 * @see #isGroupedWithParent 988 */ 989 public void setGroupedWithParent(boolean groupedWithParent) { 990 mGroupedWithParent = groupedWithParent; 991 } 992 993 private void destroyNativePageInternal(NativePage nativePage) { 994 if (nativePage == null) return; 995 assert nativePage != mNativePage : "Attempting to destroy active page."; 996 997 nativePage.destroy(); 998 } 999 1000 /** 1001 * Destroys the current {@link ContentViewCore}. 1002 * @param deleteNativeWebContents Whether or not to delete the native WebContents pointer. 1003 */ 1004 protected final void destroyContentViewCore(boolean deleteNativeWebContents) { 1005 if (mContentViewCore == null) return; 1006 1007 destroyContentViewCoreInternal(mContentViewCore); 1008 1009 if (mInfoBarContainer != null && mInfoBarContainer.getParent() != null) { 1010 mInfoBarContainer.removeFromParentView(); 1011 } 1012 mContentViewCore.destroy(); 1013 1014 mContentViewCore = null; 1015 mWebContentsDelegate = null; 1016 mWebContentsObserver = null; 1017 mVoiceSearchTabHelper = null; 1018 1019 assert mNativeTabAndroid != 0; 1020 nativeDestroyWebContents(mNativeTabAndroid, deleteNativeWebContents); 1021 } 1022 1023 /** 1024 * Gives subclasses the chance to clean up some state associated with this 1025 * {@link ContentViewCore}. This is because {@link #getContentViewCore()} 1026 * can return {@code null} if a {@link NativePage} is showing. 1027 * 1028 * @param cvc The {@link ContentViewCore} that should have associated state 1029 * cleaned up. 1030 */ 1031 protected void destroyContentViewCoreInternal(ContentViewCore cvc) { 1032 } 1033 1034 /** 1035 * A helper method to allow subclasses to build their own delegate. 1036 * @return An instance of a {@link TabChromeWebContentsDelegateAndroid}. 1037 */ 1038 protected TabChromeWebContentsDelegateAndroid createWebContentsDelegate() { 1039 return new TabChromeWebContentsDelegateAndroid(); 1040 } 1041 1042 /** 1043 * A helper method to allow subclasses to handle the Instant support 1044 * disabled event. 1045 */ 1046 @CalledByNative 1047 private void onWebContentsInstantSupportDisabled() { 1048 for (TabObserver observer : mObservers) observer.onWebContentsInstantSupportDisabled(); 1049 } 1050 1051 /** 1052 * A helper method to allow subclasses to build their own menu populator. 1053 * @return An instance of a {@link ContextMenuPopulator}. 1054 */ 1055 protected ContextMenuPopulator createContextMenuPopulator() { 1056 return new ChromeContextMenuPopulator(new TabChromeContextMenuItemDelegate()); 1057 } 1058 1059 /** 1060 * @return The {@link WindowAndroid} associated with this {@link Tab}. 1061 */ 1062 public WindowAndroid getWindowAndroid() { 1063 return mWindowAndroid; 1064 } 1065 1066 /** 1067 * @return The current {@link TabChromeWebContentsDelegateAndroid} instance. 1068 */ 1069 protected TabChromeWebContentsDelegateAndroid getChromeWebContentsDelegateAndroid() { 1070 return mWebContentsDelegate; 1071 } 1072 1073 /** 1074 * Called when the favicon of the content this tab represents changes. 1075 */ 1076 @CalledByNative 1077 protected void onFaviconUpdated() { 1078 mFavicon = null; 1079 mFaviconUrl = null; 1080 for (TabObserver observer : mObservers) observer.onFaviconUpdated(this); 1081 } 1082 1083 /** 1084 * Called when the navigation entry containing the historyitem changed, 1085 * for example because of a scroll offset or form field change. 1086 */ 1087 @CalledByNative 1088 protected void onNavEntryChanged() { 1089 } 1090 1091 /** 1092 * @return The native pointer representing the native side of this {@link Tab} object. 1093 */ 1094 @CalledByNative 1095 protected long getNativePtr() { 1096 return mNativeTabAndroid; 1097 } 1098 1099 /** This is currently called when committing a pre-rendered page. */ 1100 @CalledByNative 1101 private void swapWebContents( 1102 long newWebContents, boolean didStartLoad, boolean didFinishLoad) { 1103 ContentViewCore cvc = new ContentViewCore(mContext); 1104 ContentView cv = ContentView.newInstance(mContext, cvc); 1105 cvc.initialize(cv, cv, newWebContents, getWindowAndroid()); 1106 swapContentViewCore(cvc, false, didStartLoad, didFinishLoad); 1107 } 1108 1109 /** 1110 * Called to swap out the current view with the one passed in. 1111 * 1112 * @param newContentViewCore The content view that should be swapped into the tab. 1113 * @param deleteOldNativeWebContents Whether to delete the native web 1114 * contents of old view. 1115 * @param didStartLoad Whether 1116 * WebContentsObserver::DidStartProvisionalLoadForFrame() has 1117 * already been called. 1118 * @param didFinishLoad Whether WebContentsObserver::DidFinishLoad() has 1119 * already been called. 1120 */ 1121 protected void swapContentViewCore(ContentViewCore newContentViewCore, 1122 boolean deleteOldNativeWebContents, boolean didStartLoad, boolean didFinishLoad) { 1123 int originalWidth = 0; 1124 int originalHeight = 0; 1125 if (mContentViewCore != null) { 1126 originalWidth = mContentViewCore.getViewportWidthPix(); 1127 originalHeight = mContentViewCore.getViewportHeightPix(); 1128 mContentViewCore.onHide(); 1129 } 1130 destroyContentViewCore(deleteOldNativeWebContents); 1131 NativePage previousNativePage = mNativePage; 1132 mNativePage = null; 1133 setContentViewCore(newContentViewCore); 1134 // Size of the new ContentViewCore is zero at this point. If we don't call onSizeChanged(), 1135 // next onShow() call would send a resize message with the current ContentViewCore size 1136 // (zero) to the renderer process, although the new size will be set soon. 1137 // However, this size fluttering may confuse Blink and rendered result can be broken 1138 // (see http://crbug.com/340987). 1139 mContentViewCore.onSizeChanged(originalWidth, originalHeight, 0, 0); 1140 mContentViewCore.onShow(); 1141 mContentViewCore.attachImeAdapter(); 1142 destroyNativePageInternal(previousNativePage); 1143 for (TabObserver observer : mObservers) { 1144 observer.onWebContentsSwapped(this, didStartLoad, didFinishLoad); 1145 } 1146 } 1147 1148 @CalledByNative 1149 private void clearNativePtr() { 1150 assert mNativeTabAndroid != 0; 1151 mNativeTabAndroid = 0; 1152 } 1153 1154 @CalledByNative 1155 private void setNativePtr(long nativePtr) { 1156 assert mNativeTabAndroid == 0; 1157 mNativeTabAndroid = nativePtr; 1158 } 1159 1160 @CalledByNative 1161 private long getNativeInfoBarContainer() { 1162 return getInfoBarContainer().getNative(); 1163 } 1164 1165 /** 1166 * Validates {@code id} and increments the internal counter to make sure future ids don't 1167 * collide. 1168 * @param id The current id. Maybe {@link #INVALID_TAB_ID}. 1169 * @return A new id if {@code id} was {@link #INVALID_TAB_ID}, or {@code id}. 1170 */ 1171 public static int generateValidId(int id) { 1172 if (id == INVALID_TAB_ID) id = generateNextId(); 1173 incrementIdCounterTo(id + 1); 1174 1175 return id; 1176 } 1177 1178 /** 1179 * @return An unused id. 1180 */ 1181 private static int generateNextId() { 1182 return sIdCounter.getAndIncrement(); 1183 } 1184 1185 private void pushNativePageStateToNavigationEntry() { 1186 assert mNativeTabAndroid != 0 && getNativePage() != null; 1187 nativeSetActiveNavigationEntryTitleForUrl(mNativeTabAndroid, getNativePage().getUrl(), 1188 getNativePage().getTitle()); 1189 } 1190 1191 /** 1192 * Ensures the counter is at least as high as the specified value. The counter should always 1193 * point to an unused ID (which will be handed out next time a request comes in). Exposed so 1194 * that anything externally loading tabs and ids can set enforce new tabs start at the correct 1195 * id. 1196 * TODO(aurimas): Investigate reducing the visiblity of this method. 1197 * @param id The minimum id we should hand out to the next new tab. 1198 */ 1199 public static void incrementIdCounterTo(int id) { 1200 int diff = id - sIdCounter.get(); 1201 if (diff <= 0) return; 1202 // It's possible idCounter has been incremented between the get above and the add below 1203 // but that's OK, because in the worst case we'll overly increment idCounter. 1204 sIdCounter.addAndGet(diff); 1205 } 1206 1207 private native void nativeInit(); 1208 private native void nativeDestroy(long nativeTabAndroid); 1209 private native void nativeInitWebContents(long nativeTabAndroid, boolean incognito, 1210 ContentViewCore contentViewCore, ChromeWebContentsDelegateAndroid delegate, 1211 ContextMenuPopulator contextMenuPopulator); 1212 private native void nativeDestroyWebContents(long nativeTabAndroid, boolean deleteNative); 1213 private native Profile nativeGetProfileAndroid(long nativeTabAndroid); 1214 private native int nativeLoadUrl(long nativeTabAndroid, String url, String extraHeaders, 1215 byte[] postData, int transition, String referrerUrl, int referrerPolicy, 1216 boolean isRendererInitiated); 1217 private native int nativeGetSecurityLevel(long nativeTabAndroid); 1218 private native void nativeSetActiveNavigationEntryTitleForUrl(long nativeTabAndroid, String url, 1219 String title); 1220 private native boolean nativePrint(long nativeTabAndroid); 1221 private native Bitmap nativeGetFavicon(long nativeTabAndroid); 1222 private native boolean nativeIsFaviconValid(long nativeTabAndroid); 1223 } 1224