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