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.tab; 6 7 import android.annotation.SuppressLint; 8 import android.app.Activity; 9 import android.content.Context; 10 import android.graphics.Rect; 11 import android.text.TextUtils; 12 import android.view.View; 13 import android.view.View.OnAttachStateChangeListener; 14 import android.view.accessibility.AccessibilityEvent; 15 16 import androidx.annotation.Nullable; 17 import androidx.annotation.VisibleForTesting; 18 19 import org.chromium.base.ContextUtils; 20 import org.chromium.base.Log; 21 import org.chromium.base.ObserverList; 22 import org.chromium.base.ObserverList.RewindableIterator; 23 import org.chromium.base.TraceEvent; 24 import org.chromium.base.UserDataHost; 25 import org.chromium.base.annotations.CalledByNative; 26 import org.chromium.base.annotations.NativeMethods; 27 import org.chromium.base.supplier.ObservableSupplierImpl; 28 import org.chromium.chrome.R; 29 import org.chromium.chrome.browser.WarmupManager; 30 import org.chromium.chrome.browser.WebContentsFactory; 31 import org.chromium.chrome.browser.app.ChromeActivity; 32 import org.chromium.chrome.browser.content.ContentUtils; 33 import org.chromium.chrome.browser.contextmenu.ContextMenuPopulatorFactory; 34 import org.chromium.chrome.browser.flags.CachedFeatureFlags; 35 import org.chromium.chrome.browser.flags.ChromeFeatureList; 36 import org.chromium.chrome.browser.native_page.NativePageAssassin; 37 import org.chromium.chrome.browser.night_mode.NightModeUtils; 38 import org.chromium.chrome.browser.offlinepages.OfflinePageUtils; 39 import org.chromium.chrome.browser.paint_preview.StartupPaintPreviewHelper; 40 import org.chromium.chrome.browser.rlz.RevenueStats; 41 import org.chromium.chrome.browser.tab.state.CriticalPersistedTabData; 42 import org.chromium.chrome.browser.ui.TabObscuringHandler; 43 import org.chromium.chrome.browser.ui.native_page.FrozenNativePage; 44 import org.chromium.chrome.browser.ui.native_page.NativePage; 45 import org.chromium.chrome.browser.version.ChromeVersionInfo; 46 import org.chromium.chrome.browser.vr.VrModuleProvider; 47 import org.chromium.components.dom_distiller.core.DomDistillerUrlUtils; 48 import org.chromium.components.embedder_support.util.UrlConstants; 49 import org.chromium.components.embedder_support.view.ContentView; 50 import org.chromium.content_public.browser.ChildProcessImportance; 51 import org.chromium.content_public.browser.LoadUrlParams; 52 import org.chromium.content_public.browser.WebContents; 53 import org.chromium.content_public.browser.WebContentsAccessibility; 54 import org.chromium.content_public.common.ResourceRequestBody; 55 import org.chromium.ui.base.PageTransition; 56 import org.chromium.ui.base.WindowAndroid; 57 import org.chromium.ui.util.ColorUtils; 58 import org.chromium.url.GURL; 59 import org.chromium.url.Origin; 60 61 /** 62 * Implementation of the interface {@link Tab}. Contains and manages a {@link ContentView}. 63 * This class is not intended to be extended. 64 */ 65 public class TabImpl implements Tab, TabObscuringHandler.Observer { 66 private static final long INVALID_TIMESTAMP = -1; 67 68 /** Used for logging. */ 69 private static final String TAG = "Tab"; 70 71 private static final String PRODUCT_VERSION = ChromeVersionInfo.getProductVersion(); 72 73 private long mNativeTabAndroid; 74 75 /** Unique id of this tab (within its container). */ 76 private final int mId; 77 78 /** Whether or not this tab is an incognito tab. */ 79 private final boolean mIncognito; 80 81 /** 82 * An Application {@link Context}. Unlike {@link #mActivity}, this is the only one that is 83 * publicly exposed to help prevent leaking the {@link Activity}. 84 */ 85 private final Context mThemedApplicationContext; 86 87 /** Gives {@link Tab} a way to interact with the Android window. */ 88 private WindowAndroid mWindowAndroid; 89 90 /** The current native page (e.g. chrome-native://newtab), or {@code null} if there is none. */ 91 private NativePage mNativePage; 92 93 /** {@link WebContents} showing the current page, or {@code null} if the tab is frozen. */ 94 private WebContents mWebContents; 95 96 /** The parent view of the ContentView and the InfoBarContainer. */ 97 private ContentView mContentView; 98 99 /** The view provided by {@link TabViewManager} to be shown on top of Content view. */ 100 private View mCustomView; 101 102 /** 103 * The {@link TabViewManager} associated with this Tab that is responsible for managing custom 104 * views. 105 */ 106 private TabViewManagerImpl mTabViewManager; 107 108 /** A list of Tab observers. These are used to broadcast Tab events to listeners. */ 109 private final ObserverList<TabObserver> mObservers = new ObserverList<>(); 110 111 // Content layer Delegates 112 private TabWebContentsDelegateAndroidImpl mWebContentsDelegate; 113 114 /** 115 * Tab id to be used as a source tab in SyncedTabDelegate. 116 */ 117 private final int mSourceTabId; 118 119 private boolean mIsClosing; 120 private boolean mIsShowingErrorPage; 121 122 /** Whether or not the TabState has changed. */ 123 private boolean mIsTabStateDirty = true; 124 125 /** 126 * Saves how this tab was launched (from a link, external app, etc) so that 127 * we can determine the different circumstances in which it should be 128 * closed. For example, a tab opened from an external app should be closed 129 * when the back stack is empty and the user uses the back hardware key. A 130 * standard tab however should be kept open and the entire activity should 131 * be moved to the background. 132 */ 133 private final @Nullable @TabLaunchType Integer mLaunchType; 134 135 private @Nullable @TabCreationState Integer mCreationState; 136 137 /** 138 * URL load to be performed lazily when the Tab is next shown. 139 */ 140 private LoadUrlParams mPendingLoadParams; 141 142 /** 143 * True while a page load is in progress. 144 */ 145 private boolean mIsLoading; 146 147 /** 148 * True while a restore page load is in progress. 149 */ 150 private boolean mIsBeingRestored; 151 152 /** 153 * Whether or not the Tab is currently visible to the user. 154 */ 155 private boolean mIsHidden = true; 156 157 /** 158 * Importance of the WebContents currently attached to this tab. Note the key difference from 159 * |mIsHidden| is that a tab is hidden when the application is hidden, but the importance is 160 * not affected by this signal. 161 */ 162 private @ChildProcessImportance int mImportance = ChildProcessImportance.NORMAL; 163 164 /** Whether the renderer is currently unresponsive. */ 165 private boolean mIsRendererUnresponsive; 166 167 /** 168 * Whether didCommitProvisionalLoadForFrame() hasn't yet been called for the current native page 169 * (page A). To decrease latency, we show native pages in both loadUrl() and 170 * didCommitProvisionalLoadForFrame(). However, we mustn't show a new native page (page B) in 171 * loadUrl() if the current native page hasn't yet been committed. Otherwise, we'll show each 172 * page twice (A, B, A, B): the first two times in loadUrl(), the second two times in 173 * didCommitProvisionalLoadForFrame(). 174 */ 175 private boolean mIsNativePageCommitPending; 176 177 private TabDelegateFactory mDelegateFactory; 178 179 /** Listens for views related to the tab to be attached or detached. */ 180 private OnAttachStateChangeListener mAttachStateChangeListener; 181 182 /** Whether the tab can currently be interacted with. */ 183 private boolean mInteractableState; 184 185 /** Whether or not the tab's active view is attached to the window. */ 186 private boolean mIsViewAttachedToWindow; 187 188 private final UserDataHost mUserDataHost = new UserDataHost(); 189 190 private boolean mIsDestroyed; 191 private ObservableSupplierImpl<Boolean> mIsTabSaveEnabledSupplier = 192 new ObservableSupplierImpl<>(); 193 194 /** 195 * Creates an instance of a {@link TabImpl}. 196 * 197 * This constructor can be called before the native library has been loaded, so any additions 198 * must be vetted for library calls. 199 * 200 * Package-private. Use {@link TabBuilder} to create an instance. 201 * 202 * @param id The id this tab should be identified with. 203 * @param parent The tab that caused this tab to be opened. 204 * @param incognito Whether or not this tab is incognito. 205 * @param launchType Type indicating how this tab was launched. 206 */ 207 @SuppressLint("HandlerLeak") TabImpl(int id, Tab parent, boolean incognito, @Nullable @TabLaunchType Integer launchType)208 TabImpl(int id, Tab parent, boolean incognito, @Nullable @TabLaunchType Integer launchType) { 209 mIsTabSaveEnabledSupplier.set(false); 210 mId = TabIdManager.getInstance().generateValidId(id); 211 mIncognito = incognito; 212 if (parent == null) { 213 mSourceTabId = INVALID_TAB_ID; 214 } else { 215 CriticalPersistedTabData.from(this).setParentId(parent.getId()); 216 mSourceTabId = parent.isIncognito() == incognito ? parent.getId() : INVALID_TAB_ID; 217 } 218 219 // Override the configuration for night mode to always stay in light mode until all UIs in 220 // Tab are inflated from activity context instead of application context. This is to 221 // avoid getting the wrong night mode state when application context inherits a system UI 222 // mode different from the UI mode we need. 223 // TODO(https://crbug.com/938641): Remove this once Tab UIs are all inflated from 224 // activity. 225 mThemedApplicationContext = 226 NightModeUtils.wrapContextWithNightModeConfig(ContextUtils.getApplicationContext(), 227 ChromeActivity.getThemeId(), false /*nightMode*/); 228 229 mLaunchType = launchType; 230 231 mAttachStateChangeListener = new OnAttachStateChangeListener() { 232 @Override 233 public void onViewAttachedToWindow(View view) { 234 mIsViewAttachedToWindow = true; 235 updateInteractableState(); 236 } 237 238 @Override 239 public void onViewDetachedFromWindow(View view) { 240 mIsViewAttachedToWindow = false; 241 updateInteractableState(); 242 } 243 }; 244 mTabViewManager = new TabViewManagerImpl(this); 245 } 246 247 @Override addObserver(TabObserver observer)248 public void addObserver(TabObserver observer) { 249 mObservers.addObserver(observer); 250 } 251 252 @Override removeObserver(TabObserver observer)253 public void removeObserver(TabObserver observer) { 254 mObservers.removeObserver(observer); 255 } 256 257 @Override getUserDataHost()258 public UserDataHost getUserDataHost() { 259 return mUserDataHost; 260 } 261 262 @Override getWebContents()263 public WebContents getWebContents() { 264 return mWebContents; 265 } 266 267 @Override getContext()268 public Context getContext() { 269 if (getWindowAndroid() == null) return mThemedApplicationContext; 270 Context context = getWindowAndroid().getContext().get(); 271 return context == context.getApplicationContext() ? mThemedApplicationContext : context; 272 } 273 274 @Override getWindowAndroid()275 public WindowAndroid getWindowAndroid() { 276 return mWindowAndroid; 277 } 278 279 @Override updateAttachment( @ullable WindowAndroid window, @Nullable TabDelegateFactory tabDelegateFactory)280 public void updateAttachment( 281 @Nullable WindowAndroid window, @Nullable TabDelegateFactory tabDelegateFactory) { 282 // Non-null delegate factory while being detached is not valid. 283 assert !(window == null && tabDelegateFactory != null); 284 285 if (window != null) { 286 updateWindowAndroid(window); 287 if (tabDelegateFactory != null) setDelegateFactory(tabDelegateFactory); 288 289 // Reload the NativePage (if any), since the old NativePage has a reference to the old 290 // activity. 291 if (isNativePage()) maybeShowNativePage(getUrlString(), true); 292 } 293 294 // Notify the event to observers only when we do the reparenting task, not when we simply 295 // switch window in which case a new window is non-null but delegate is null. 296 boolean notify = (window != null && tabDelegateFactory != null) 297 || (window == null && tabDelegateFactory == null); 298 if (notify) { 299 for (TabObserver observer : mObservers) { 300 observer.onActivityAttachmentChanged(this, window); 301 } 302 } 303 } 304 305 /** 306 * Sets a custom {@link View} for this {@link Tab} that replaces Content view. 307 */ setCustomView(@ullable View view)308 void setCustomView(@Nullable View view) { 309 mCustomView = view; 310 notifyContentChanged(); 311 } 312 313 @Override getContentView()314 public ContentView getContentView() { 315 return mContentView; 316 } 317 318 @Override getView()319 public View getView() { 320 if (mCustomView != null) return mCustomView; 321 322 if (mNativePage != null) return mNativePage.getView(); 323 324 return mContentView; 325 } 326 327 @Override getTabViewManager()328 public TabViewManager getTabViewManager() { 329 return mTabViewManager; 330 } 331 332 @Override 333 @CalledByNative getId()334 public int getId() { 335 return mId; 336 } 337 338 // TODO(crbug.com/1113249) move getUrl() and getUrlString() to CriticalPersistedTabData 339 @Override getUrlString()340 public String getUrlString() { 341 return getUrl().getSpec(); 342 } 343 344 @CalledByNative 345 @Override getUrl()346 public GURL getUrl() { 347 if (!isInitialized()) { 348 return GURL.emptyGURL(); 349 } 350 GURL url = getWebContents() != null ? getWebContents().getVisibleUrl() : GURL.emptyGURL(); 351 352 // If we have a ContentView, or a NativePage, or the url is not empty, we have a WebContents 353 // so cache the WebContent's url. If not use the cached version. 354 if (getWebContents() != null || isNativePage() || !url.getSpec().isEmpty()) { 355 CriticalPersistedTabData.from(this).setUrl(url); 356 } 357 358 return CriticalPersistedTabData.from(this).getUrl() != null 359 ? CriticalPersistedTabData.from(this).getUrl() 360 : GURL.emptyGURL(); 361 } 362 363 @Override getOriginalUrl()364 public String getOriginalUrl() { 365 return DomDistillerUrlUtils.getOriginalUrlFromDistillerUrl(getUrlString()); 366 } 367 368 @CalledByNative 369 @Override 370 // TODO(crbug.com/1113834) migrate getTitle() to CriticalPersistedTabData.from(tab).getTitle() getTitle()371 public String getTitle() { 372 if (CriticalPersistedTabData.from(this).getTitle() == null) updateTitle(); 373 return CriticalPersistedTabData.from(this).getTitle(); 374 } 375 getThemedApplicationContext()376 Context getThemedApplicationContext() { 377 return mThemedApplicationContext; 378 } 379 380 @Override getNativePage()381 public NativePage getNativePage() { 382 return mNativePage; 383 } 384 385 @Override 386 @CalledByNative isNativePage()387 public boolean isNativePage() { 388 return mNativePage != null; 389 } 390 391 @Override isShowingCustomView()392 public boolean isShowingCustomView() { 393 return mCustomView != null; 394 } 395 396 @Override freezeNativePage()397 public void freezeNativePage() { 398 if (mNativePage == null || mNativePage.isFrozen() 399 || mNativePage.getView().getParent() == null) { 400 return; 401 } 402 mNativePage = FrozenNativePage.freeze(mNativePage); 403 updateInteractableState(); 404 } 405 406 @Override getLaunchType()407 public @TabLaunchType int getLaunchType() { 408 return mLaunchType; 409 } 410 411 @Override isIncognito()412 public boolean isIncognito() { 413 return mIncognito; 414 } 415 416 @Override isShowingErrorPage()417 public boolean isShowingErrorPage() { 418 return mIsShowingErrorPage; 419 } 420 421 /** 422 * @return true iff the tab doesn't hold a live page. This happens before initialize() and when 423 * the tab holds frozen WebContents state that is yet to be inflated. 424 */ 425 @Override isFrozen()426 public boolean isFrozen() { 427 return !isNativePage() && getWebContents() == null; 428 } 429 430 @CalledByNative 431 @Override isUserInteractable()432 public boolean isUserInteractable() { 433 return mInteractableState; 434 } 435 436 @Override loadUrl(LoadUrlParams params)437 public int loadUrl(LoadUrlParams params) { 438 try { 439 TraceEvent.begin("Tab.loadUrl"); 440 // TODO(tedchoc): When showing the android NTP, delay the call to 441 // TabImplJni.get().loadUrl until the android view has entirely rendered. 442 if (!mIsNativePageCommitPending) { 443 mIsNativePageCommitPending = maybeShowNativePage(params.getUrl(), false); 444 } 445 446 if ("chrome://java-crash/".equals(params.getUrl())) { 447 return handleJavaCrash(); 448 } 449 450 if (mNativeTabAndroid == 0) { 451 // if mNativeTabAndroid is null then we are going to crash anyways on the 452 // native side. Lets crash on the java side so that we can have a better stack 453 // trace. 454 throw new RuntimeException("Tab.loadUrl called when no native side exists"); 455 } 456 457 // We load the URL from the tab rather than directly from the ContentView so the tab has 458 // a chance of using a prerenderer page is any. 459 int loadType = TabImplJni.get().loadUrl(mNativeTabAndroid, TabImpl.this, 460 params.getUrl(), params.getInitiatorOrigin(), params.getVerbatimHeaders(), 461 params.getPostData(), params.getTransitionType(), 462 params.getReferrer() != null ? params.getReferrer().getUrl() : null, 463 // Policy will be ignored for null referrer url, 0 is just a placeholder. 464 // TODO(ppi): Should we pass Referrer jobject and add JNI methods to read it 465 // from the native? 466 params.getReferrer() != null ? params.getReferrer().getPolicy() : 0, 467 params.getIsRendererInitiated(), params.getShouldReplaceCurrentEntry(), 468 params.getHasUserGesture(), params.getShouldClearHistoryList(), 469 params.getInputStartTimestamp(), params.getIntentReceivedTimestamp()); 470 471 for (TabObserver observer : mObservers) { 472 observer.onLoadUrl(this, params, loadType); 473 } 474 return loadType; 475 } finally { 476 TraceEvent.end("Tab.loadUrl"); 477 } 478 } 479 480 @Override loadIfNeeded()481 public boolean loadIfNeeded() { 482 if (getActivity() == null) { 483 Log.e(TAG, "Tab couldn't be loaded because Context was null."); 484 return false; 485 } 486 487 if (mPendingLoadParams != null) { 488 assert isFrozen(); 489 WebContents webContents = WarmupManager.getInstance().takeSpareWebContents( 490 isIncognito(), isHidden(), isCustomTab()); 491 if (webContents == null) { 492 webContents = WebContentsFactory.createWebContents(isIncognito(), isHidden()); 493 } 494 initWebContents(webContents); 495 loadUrl(mPendingLoadParams); 496 mPendingLoadParams = null; 497 return true; 498 } 499 500 restoreIfNeeded(); 501 return true; 502 } 503 504 @Override reload()505 public void reload() { 506 // TODO(dtrainor): Should we try to rebuild the ContentView if it's frozen? 507 if (OfflinePageUtils.isOfflinePage(this)) { 508 // If current page is an offline page, reload it with custom behavior defined in extra 509 // header respected. 510 OfflinePageUtils.reload(getWebContents(), 511 /*loadUrlDelegate=*/new OfflinePageUtils.TabOfflinePageLoadUrlDelegate(this)); 512 } else { 513 if (getWebContents() != null) getWebContents().getNavigationController().reload(true); 514 } 515 } 516 517 @Override reloadIgnoringCache()518 public void reloadIgnoringCache() { 519 if (getWebContents() != null) { 520 getWebContents().getNavigationController().reloadBypassingCache(true); 521 } 522 } 523 524 @Override stopLoading()525 public void stopLoading() { 526 if (isLoading()) { 527 RewindableIterator<TabObserver> observers = getTabObservers(); 528 while (observers.hasNext()) { 529 observers.next().onPageLoadFinished(this, getUrlString()); 530 } 531 } 532 if (getWebContents() != null) getWebContents().stop(); 533 } 534 535 @Override needsReload()536 public boolean needsReload() { 537 return getWebContents() != null && getWebContents().getNavigationController().needsReload(); 538 } 539 540 @Override isLoading()541 public boolean isLoading() { 542 return mIsLoading; 543 } 544 545 @Override isBeingRestored()546 public boolean isBeingRestored() { 547 return mIsBeingRestored; 548 } 549 550 @Override getProgress()551 public float getProgress() { 552 return !isLoading() ? 1 : (int) mWebContents.getLoadProgress(); 553 } 554 555 @Override canGoBack()556 public boolean canGoBack() { 557 return getWebContents() != null && getWebContents().getNavigationController().canGoBack(); 558 } 559 560 @Override canGoForward()561 public boolean canGoForward() { 562 return getWebContents() != null 563 && getWebContents().getNavigationController().canGoForward(); 564 } 565 566 @Override goBack()567 public void goBack() { 568 if (getWebContents() != null) getWebContents().getNavigationController().goBack(); 569 } 570 571 @Override goForward()572 public void goForward() { 573 if (getWebContents() != null) getWebContents().getNavigationController().goForward(); 574 } 575 576 // TabLifecycle implementation. 577 578 @Override isInitialized()579 public boolean isInitialized() { 580 return mNativeTabAndroid != 0; 581 } 582 583 @Override show(@abSelectionType int type)584 public final void show(@TabSelectionType int type) { 585 try { 586 TraceEvent.begin("Tab.show"); 587 if (!isHidden()) return; 588 // Keep unsetting mIsHidden above loadIfNeeded(), so that we pass correct visibility 589 // when spawning WebContents in loadIfNeeded(). 590 mIsHidden = false; 591 updateInteractableState(); 592 593 loadIfNeeded(); 594 595 if (getWebContents() != null) getWebContents().onShow(); 596 597 // If the NativePage was frozen while in the background (see NativePageAssassin), 598 // recreate the NativePage now. 599 NativePage nativePage = getNativePage(); 600 if (nativePage != null && nativePage.isFrozen()) { 601 maybeShowNativePage(nativePage.getUrl(), true); 602 } 603 NativePageAssassin.getInstance().tabShown(this); 604 TabImportanceManager.tabShown(this); 605 606 // If the page is still loading, update the progress bar (otherwise it would not show 607 // until the renderer notifies of new progress being made). 608 if (getProgress() < 100) { 609 notifyLoadProgress(getProgress()); 610 } 611 612 for (TabObserver observer : mObservers) observer.onShown(this, type); 613 614 // Updating the timestamp has to happen after the showInternal() call since subclasses 615 // may use it for logging. 616 CriticalPersistedTabData.from(this).setTimestampMillis(System.currentTimeMillis()); 617 } finally { 618 TraceEvent.end("Tab.show"); 619 } 620 } 621 622 @Override hide(@abHidingType int type)623 public final void hide(@TabHidingType int type) { 624 try { 625 TraceEvent.begin("Tab.hide"); 626 if (isHidden()) return; 627 mIsHidden = true; 628 updateInteractableState(); 629 630 if (getWebContents() != null) getWebContents().onHide(); 631 632 // Allow this tab's NativePage to be frozen if it stays hidden for a while. 633 NativePageAssassin.getInstance().tabHidden(this); 634 635 for (TabObserver observer : mObservers) observer.onHidden(this, type); 636 } finally { 637 TraceEvent.end("Tab.hide"); 638 } 639 } 640 641 @Override isClosing()642 public boolean isClosing() { 643 return mIsClosing; 644 } 645 646 @Override setClosing(boolean closing)647 public void setClosing(boolean closing) { 648 mIsClosing = closing; 649 for (TabObserver observer : mObservers) observer.onClosingStateChanged(this, closing); 650 } 651 652 @CalledByNative 653 @Override isHidden()654 public boolean isHidden() { 655 return mIsHidden; 656 } 657 658 @Override destroy()659 public void destroy() { 660 // Update the title before destroying the tab. http://b/5783092 661 updateTitle(); 662 663 for (TabObserver observer : mObservers) observer.onDestroyed(this); 664 mObservers.clear(); 665 666 mUserDataHost.destroy(); 667 mTabViewManager.destroy(); 668 hideNativePage(false, null); 669 destroyWebContents(true); 670 671 TabImportanceManager.tabDestroyed(this); 672 673 // Destroys the native tab after destroying the ContentView but before destroying the 674 // InfoBarContainer. The native tab should be destroyed before the infobar container as 675 // destroying the native tab cleanups up any remaining infobars. The infobar container 676 // expects all infobars to be cleaned up before its own destruction. 677 if (mNativeTabAndroid != 0) { 678 TabImplJni.get().destroy(mNativeTabAndroid, TabImpl.this); 679 assert mNativeTabAndroid == 0; 680 } 681 } 682 683 /** 684 * WARNING: This method is deprecated. Consider other ways such as passing the dependencies 685 * to the constructor, rather than accessing ChromeActivity from Tab and using getters. 686 * @return {@link ChromeActivity} that currently contains this {@link Tab} in its 687 * {@link TabModel}. 688 */ 689 @Deprecated getActivity()690 ChromeActivity<?> getActivity() { 691 if (getWindowAndroid() == null) return null; 692 Activity activity = ContextUtils.activityFromContext(getWindowAndroid().getContext().get()); 693 if (activity instanceof ChromeActivity) return (ChromeActivity<?>) activity; 694 return null; 695 } 696 697 /** 698 * @param tab {@link Tab} instance being checked. 699 * @return Whether the tab is detached from any Activity and its {@link WindowAndroid}. 700 * Certain functionalities will not work until it is attached to an activity 701 * with {@link ReparentingTask#finish}. 702 */ isDetached(Tab tab)703 static boolean isDetached(Tab tab) { 704 if (tab.getWebContents() == null) return true; 705 // Should get WindowAndroid from WebContents since the one from |getWindowAndroid()| 706 // is always non-null even when the tab is in detached state. See the comment in |detach()|. 707 WindowAndroid window = tab.getWebContents().getTopLevelNativeWindow(); 708 if (window == null) return true; 709 Activity activity = ContextUtils.activityFromContext(window.getContext().get()); 710 return !(activity instanceof ChromeActivity); 711 } 712 713 /** 714 * @return Whether the TabState representing this Tab has been updated. 715 */ isTabStateDirty()716 public boolean isTabStateDirty() { 717 return mIsTabStateDirty; 718 } 719 720 @Override setIsTabStateDirty(boolean isDirty)721 public void setIsTabStateDirty(boolean isDirty) { 722 mIsTabStateDirty = isDirty; 723 } 724 725 @Override setAddApi2TransitionToFutureNavigations(boolean shouldAdd)726 public void setAddApi2TransitionToFutureNavigations(boolean shouldAdd) { 727 if (mNativeTabAndroid != 0) { 728 TabImplJni.get().setAddApi2TransitionToFutureNavigations(mNativeTabAndroid, shouldAdd); 729 } 730 } 731 732 @Override getAddApi2TransitionToFutureNavigations()733 public boolean getAddApi2TransitionToFutureNavigations() { 734 return (mNativeTabAndroid != 0) 735 && TabImplJni.get().getAddApi2TransitionToFutureNavigations(mNativeTabAndroid); 736 } 737 738 @Override setHideFutureNavigations(boolean hide)739 public void setHideFutureNavigations(boolean hide) { 740 if (mNativeTabAndroid != 0) { 741 TabImplJni.get().setHideFutureNavigations(mNativeTabAndroid, hide); 742 } 743 } 744 745 @Override getHideFutureNavigations()746 public boolean getHideFutureNavigations() { 747 return (mNativeTabAndroid != 0) 748 && TabImplJni.get().getHideFutureNavigations(mNativeTabAndroid); 749 } 750 751 @Override setIsTabSaveEnabled(boolean isTabSaveEnabled)752 public void setIsTabSaveEnabled(boolean isTabSaveEnabled) { 753 mIsTabSaveEnabledSupplier.set(isTabSaveEnabled); 754 } 755 756 // TabObscuringHandler.Observer 757 758 @Override updateObscured(boolean isObscured)759 public void updateObscured(boolean isObscured) { 760 // Update whether or not the current native tab and/or web contents are 761 // currently visible (from an accessibility perspective), or whether 762 // they're obscured by another view. 763 View view = getView(); 764 if (view != null) { 765 int importantForAccessibility = isObscured 766 ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS 767 : View.IMPORTANT_FOR_ACCESSIBILITY_YES; 768 if (view.getImportantForAccessibility() != importantForAccessibility) { 769 view.setImportantForAccessibility(importantForAccessibility); 770 view.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); 771 } 772 } 773 774 WebContentsAccessibility wcax = getWebContentsAccessibility(getWebContents()); 775 if (wcax != null) { 776 boolean isWebContentObscured = isObscured || isShowingCustomView(); 777 wcax.setObscuredByAnotherView(isWebContentObscured); 778 } 779 } 780 781 /** 782 * Initializes {@link Tab} with {@code webContents}. If {@code webContents} is {@code null} 783 * a new {@link WebContents} will be created for this {@link Tab}. 784 * @param parent The tab that caused this tab to be opened. 785 * @param creationState State in which the tab is created. 786 * @param loadUrlParams Parameters used for a lazily loaded Tab. 787 * @param webContents A {@link WebContents} object or {@code null} if one should be created. 788 * @param delegateFactory The {@link TabDelegateFactory} to be used for delegate creation. 789 * @param initiallyHidden Only used if {@code webContents} is {@code null}. Determines 790 * whether or not the newly created {@link WebContents} will be hidden or not. 791 * @param tabState State containing information about this Tab, if it was persisted. 792 * @param serializedCriticalPersistedTabData {@link CriticalPersistedTabData} in serialized 793 * form. {@link CriticalPersistedTabData} is a replacement for {@link TabState} 794 */ initialize(Tab parent, @Nullable @TabCreationState Integer creationState, LoadUrlParams loadUrlParams, WebContents webContents, @Nullable TabDelegateFactory delegateFactory, boolean initiallyHidden, TabState tabState, @Nullable byte[] serializedCriticalPersistedTabData)795 void initialize(Tab parent, @Nullable @TabCreationState Integer creationState, 796 LoadUrlParams loadUrlParams, WebContents webContents, 797 @Nullable TabDelegateFactory delegateFactory, boolean initiallyHidden, 798 TabState tabState, @Nullable byte[] serializedCriticalPersistedTabData) { 799 try { 800 TraceEvent.begin("Tab.initialize"); 801 802 CriticalPersistedTabData.from(this).setLaunchTypeAtCreation(mLaunchType); 803 mCreationState = creationState; 804 mPendingLoadParams = loadUrlParams; 805 if (loadUrlParams != null) { 806 CriticalPersistedTabData.from(this).setUrl(new GURL(loadUrlParams.getUrl())); 807 } 808 809 TabHelpers.initTabHelpers(this, parent); 810 811 if (serializedCriticalPersistedTabData != null && useCriticalPersistedTabData()) { 812 CriticalPersistedTabData.build(this, serializedCriticalPersistedTabData, true); 813 } else if (tabState != null) { 814 restoreFieldsFromState(tabState); 815 } 816 817 initializeNative(); 818 819 mDelegateFactory = delegateFactory; 820 RevenueStats.getInstance().tabCreated(this); 821 822 // If there is a frozen WebContents state or a pending lazy load, don't create a new 823 // WebContents. Restoring will be done when showing the tab in the foreground. 824 if (CriticalPersistedTabData.from(this).getWebContentsState() != null 825 || getPendingLoadParams() != null) { 826 return; 827 } 828 829 boolean creatingWebContents = webContents == null; 830 if (creatingWebContents) { 831 webContents = WarmupManager.getInstance().takeSpareWebContents( 832 isIncognito(), initiallyHidden, isCustomTab()); 833 if (webContents == null) { 834 webContents = 835 WebContentsFactory.createWebContents(isIncognito(), initiallyHidden); 836 } 837 } 838 839 initWebContents(webContents); 840 841 if (!creatingWebContents && webContents.isLoadingToDifferentDocument()) { 842 didStartPageLoad(webContents.getVisibleUrl()); 843 } 844 845 } finally { 846 if (CriticalPersistedTabData.from(this).getTimestampMillis() == INVALID_TIMESTAMP) { 847 CriticalPersistedTabData.from(this).setTimestampMillis(System.currentTimeMillis()); 848 } 849 registerTabSaving(); 850 String appId; 851 Boolean hasThemeColor; 852 int themeColor; 853 if (serializedCriticalPersistedTabData != null && useCriticalPersistedTabData()) { 854 appId = CriticalPersistedTabData.from(this).getOpenerAppId(); 855 themeColor = CriticalPersistedTabData.from(this).getThemeColor(); 856 hasThemeColor = themeColor != TabState.UNSPECIFIED_THEME_COLOR 857 && ColorUtils.isValidThemeColor(themeColor); 858 } else { 859 appId = tabState != null ? tabState.openerAppId : null; 860 hasThemeColor = tabState != null ? tabState.hasThemeColor() : null; 861 themeColor = tabState != null ? tabState.getThemeColor() : 0; 862 } 863 for (TabObserver observer : mObservers) { 864 observer.onInitialized(this, appId, hasThemeColor, themeColor); 865 } 866 TraceEvent.end("Tab.initialize"); 867 } 868 } 869 870 @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) registerTabSaving()871 public void registerTabSaving() { 872 CriticalPersistedTabData.from(this).registerIsTabSaveEnabledSupplier( 873 mIsTabSaveEnabledSupplier); 874 } 875 useCriticalPersistedTabData()876 private boolean useCriticalPersistedTabData() { 877 return CachedFeatureFlags.isEnabled(ChromeFeatureList.CRITICAL_PERSISTED_TAB_DATA); 878 } 879 880 @Nullable 881 @TabCreationState getCreationState()882 Integer getCreationState() { 883 return mCreationState; 884 } 885 886 /** 887 * Restores member fields from the given TabState. 888 * @param state TabState containing information about this Tab. 889 */ restoreFieldsFromState(TabState state)890 void restoreFieldsFromState(TabState state) { 891 assert state != null; 892 CriticalPersistedTabData.from(this).setWebContentsState(state.contentsState); 893 CriticalPersistedTabData.from(this).setTimestampMillis(state.timestampMillis); 894 CriticalPersistedTabData.from(this).setUrl( 895 new GURL(state.contentsState.getVirtualUrlFromState())); 896 CriticalPersistedTabData.from(this).setTitle( 897 state.contentsState.getDisplayTitleFromState()); 898 CriticalPersistedTabData.from(this).setLaunchTypeAtCreation(state.tabLaunchTypeAtCreation); 899 CriticalPersistedTabData.from(this).setRootId( 900 state.rootId == Tab.INVALID_TAB_ID ? mId : state.rootId); 901 } 902 903 /** 904 * @return An {@link ObserverList.RewindableIterator} instance that points to all of 905 * the current {@link TabObserver}s on this class. Note that calling 906 * {@link java.util.Iterator#remove()} will throw an 907 * {@link UnsupportedOperationException}. 908 */ getTabObservers()909 ObserverList.RewindableIterator<TabObserver> getTabObservers() { 910 return mObservers.rewindableIterator(); 911 } 912 setImportance(@hildProcessImportance int importance)913 final void setImportance(@ChildProcessImportance int importance) { 914 if (mImportance == importance) return; 915 mImportance = importance; 916 WebContents webContents = getWebContents(); 917 if (webContents == null) return; 918 webContents.setImportance(mImportance); 919 } 920 921 /** 922 * Hides the current {@link NativePage}, if any, and shows the {@link WebContents}'s view. 923 */ showRenderedPage()924 void showRenderedPage() { 925 updateTitle(); 926 if (mNativePage != null) hideNativePage(true, null); 927 } 928 updateWindowAndroid(WindowAndroid windowAndroid)929 void updateWindowAndroid(WindowAndroid windowAndroid) { 930 // TODO(yusufo): mWindowAndroid can never be null until crbug.com/657007 is fixed. 931 assert windowAndroid != null; 932 mWindowAndroid = windowAndroid; 933 WebContents webContents = getWebContents(); 934 if (webContents != null) webContents.setTopLevelNativeWindow(mWindowAndroid); 935 } 936 getDelegateFactory()937 TabDelegateFactory getDelegateFactory() { 938 return mDelegateFactory; 939 } 940 941 @VisibleForTesting getTabWebContentsDelegateAndroid()942 TabWebContentsDelegateAndroidImpl getTabWebContentsDelegateAndroid() { 943 return mWebContentsDelegate; 944 } 945 946 // Forwarded from TabWebContentsDelegateAndroid. 947 948 /** 949 * Called when a navigation begins and no navigation was in progress 950 * @param toDifferentDocument Whether this navigation will transition between 951 * documents (i.e., not a fragment navigation or JS History API call). 952 */ onLoadStarted(boolean toDifferentDocument)953 void onLoadStarted(boolean toDifferentDocument) { 954 if (toDifferentDocument) mIsLoading = true; 955 for (TabObserver observer : mObservers) observer.onLoadStarted(this, toDifferentDocument); 956 } 957 958 /** 959 * Called when a navigation completes and no other navigation is in progress. 960 */ onLoadStopped()961 void onLoadStopped() { 962 // mIsLoading should only be false if this is a same-document navigation. 963 boolean toDifferentDocument = mIsLoading; 964 mIsLoading = false; 965 for (TabObserver observer : mObservers) observer.onLoadStopped(this, toDifferentDocument); 966 } 967 handleRendererResponsiveStateChanged(boolean isResponsive)968 void handleRendererResponsiveStateChanged(boolean isResponsive) { 969 mIsRendererUnresponsive = !isResponsive; 970 for (TabObserver observer : mObservers) { 971 observer.onRendererResponsiveStateChanged(this, isResponsive); 972 } 973 } 974 975 // Forwarded from TabWebContentsObserver. 976 977 /** 978 * Called when a page has started loading. 979 * @param validatedUrl URL being loaded. 980 */ didStartPageLoad(GURL validatedUrl)981 void didStartPageLoad(GURL validatedUrl) { 982 updateTitle(); 983 if (mIsRendererUnresponsive) handleRendererResponsiveStateChanged(true); 984 for (TabObserver observer : mObservers) { 985 observer.onPageLoadStarted(this, validatedUrl.getSpec()); 986 } 987 } 988 989 /** 990 * Called when a page has finished loading. 991 * @param url URL that was loaded. 992 */ didFinishPageLoad(String url)993 void didFinishPageLoad(String url) { 994 mIsTabStateDirty = true; 995 updateTitle(); 996 997 for (TabObserver observer : mObservers) observer.onPageLoadFinished(this, url); 998 mIsBeingRestored = false; 999 } 1000 1001 /** 1002 * Called when a page has failed loading. 1003 * @param errorCode The error code causing the page to fail loading. 1004 */ didFailPageLoad(int errorCode)1005 void didFailPageLoad(int errorCode) { 1006 for (TabObserver observer : mObservers) { 1007 observer.onPageLoadFailed(this, errorCode); 1008 } 1009 mIsBeingRestored = false; 1010 } 1011 1012 /** 1013 * Update internal Tab state when provisional load gets committed. 1014 * @param url The URL that was loaded. 1015 * @param transitionType The transition type to the current URL. 1016 */ handleDidFinishNavigation(GURL url, Integer transitionType)1017 void handleDidFinishNavigation(GURL url, Integer transitionType) { 1018 mIsNativePageCommitPending = false; 1019 boolean isReload = (transitionType != null 1020 && (transitionType & PageTransition.CORE_MASK) == PageTransition.RELOAD); 1021 if (!maybeShowNativePage(url.getSpec(), isReload)) { 1022 showRenderedPage(); 1023 } 1024 } 1025 1026 /** 1027 * Notify the observers that the load progress has changed. 1028 * @param progress The current percentage of progress. 1029 */ notifyLoadProgress(float progress)1030 void notifyLoadProgress(float progress) { 1031 for (TabObserver observer : mObservers) observer.onLoadProgressChanged(this, progress); 1032 } 1033 1034 /** 1035 * Add a new navigation entry for the current URL and page title. 1036 */ pushNativePageStateToNavigationEntry()1037 void pushNativePageStateToNavigationEntry() { 1038 assert mNativeTabAndroid != 0 && getNativePage() != null; 1039 TabImplJni.get().setActiveNavigationEntryTitleForUrl(mNativeTabAndroid, TabImpl.this, 1040 getNativePage().getUrl(), getNativePage().getTitle()); 1041 } 1042 1043 /** 1044 * Set whether the Tab needs to be reloaded. 1045 */ setNeedsReload()1046 void setNeedsReload() { 1047 assert getWebContents() != null; 1048 getWebContents().getNavigationController().setNeedsReload(); 1049 } 1050 1051 /** 1052 * Called when navigation entries were removed. 1053 */ notifyNavigationEntriesDeleted()1054 void notifyNavigationEntriesDeleted() { 1055 mIsTabStateDirty = true; 1056 for (TabObserver observer : mObservers) observer.onNavigationEntriesDeleted(this); 1057 } 1058 1059 ////////////// 1060 1061 /** 1062 * @return Whether the renderer is currently unresponsive. 1063 */ isRendererUnresponsive()1064 boolean isRendererUnresponsive() { 1065 return mIsRendererUnresponsive; 1066 } 1067 1068 /** 1069 * Load the original image (uncompressed by spdy proxy) in this tab. 1070 */ loadOriginalImage()1071 void loadOriginalImage() { 1072 if (mNativeTabAndroid != 0) { 1073 TabImplJni.get().loadOriginalImage(mNativeTabAndroid, TabImpl.this); 1074 } 1075 } 1076 1077 /** 1078 * Sets whether the tab is showing an error page. This is reset whenever the tab finishes a 1079 * navigation. 1080 * Note: This is kept here to keep the build green. Remove from interface as soon as 1081 * the downstream patch lands. 1082 * @param isShowingErrorPage Whether the tab shows an error page. 1083 */ setIsShowingErrorPage(boolean isShowingErrorPage)1084 void setIsShowingErrorPage(boolean isShowingErrorPage) { 1085 mIsShowingErrorPage = isShowingErrorPage; 1086 } 1087 1088 /** 1089 * Shows a native page for url if it's a valid chrome-native URL. Otherwise, does nothing. 1090 * @param url The url of the current navigation. 1091 * @param forceReload If true, the current native page (if any) will not be reused, even if it 1092 * matches the URL. 1093 * @return True, if a native page was displayed for url. 1094 */ maybeShowNativePage(String url, boolean forceReload)1095 boolean maybeShowNativePage(String url, boolean forceReload) { 1096 // While detached for reparenting we don't have an owning Activity, or TabModelSelector, 1097 // so we can't create the native page. The native page will be created once reparenting is 1098 // completed. 1099 if (isDetached(this)) return false; 1100 NativePage candidateForReuse = forceReload ? null : getNativePage(); 1101 NativePage nativePage = mDelegateFactory.createNativePage(url, candidateForReuse, this); 1102 if (nativePage != null) { 1103 showNativePage(nativePage); 1104 notifyPageTitleChanged(); 1105 notifyFaviconChanged(); 1106 return true; 1107 } 1108 return false; 1109 } 1110 1111 /** 1112 * Calls onContentChanged on all TabObservers and updates accessibility visibility. 1113 */ notifyContentChanged()1114 void notifyContentChanged() { 1115 for (TabObserver observer : mObservers) observer.onContentChanged(this); 1116 } 1117 notifyThemeColorChanged(int themeColor)1118 void notifyThemeColorChanged(int themeColor) { 1119 RewindableIterator<TabObserver> observers = getTabObservers(); 1120 while (observers.hasNext()) { 1121 observers.next().onDidChangeThemeColor(this, themeColor); 1122 } 1123 } 1124 updateTitle()1125 void updateTitle() { 1126 if (isFrozen()) return; 1127 1128 // When restoring the tabs, the title will no longer be populated, so request it from the 1129 // WebContents or NativePage (if present). 1130 String title = ""; 1131 if (isNativePage()) { 1132 title = mNativePage.getTitle(); 1133 } else if (getWebContents() != null) { 1134 title = getWebContents().getTitle(); 1135 } 1136 updateTitle(title); 1137 } 1138 1139 /** 1140 * Cache the title for the current page. 1141 * 1142 * {@link ContentViewClient#onUpdateTitle} is unreliable, particularly for navigating backwards 1143 * and forwards in the history stack, so pull the correct title whenever the page changes. 1144 * onUpdateTitle is only called when the title of a navigation entry changes. When the user goes 1145 * back a page the navigation entry exists with the correct title, thus the title is not 1146 * actually changed, and no notification is sent. 1147 * @param title Title of the page. 1148 */ updateTitle(String title)1149 void updateTitle(String title) { 1150 if (TextUtils.equals(CriticalPersistedTabData.from(this).getTitle(), title)) return; 1151 1152 mIsTabStateDirty = true; 1153 CriticalPersistedTabData.from(this).setTitle(title); 1154 notifyPageTitleChanged(); 1155 } 1156 1157 @Override getPendingLoadParams()1158 public LoadUrlParams getPendingLoadParams() { 1159 return mPendingLoadParams; 1160 } 1161 1162 @Override setShouldBlockNewNotificationRequests(boolean value)1163 public void setShouldBlockNewNotificationRequests(boolean value) { 1164 if (mNativeTabAndroid != 0) { 1165 TabImplJni.get().setShouldBlockNewNotificationRequests(mNativeTabAndroid, value); 1166 } 1167 } 1168 1169 @Override getShouldBlockNewNotificationRequests()1170 public boolean getShouldBlockNewNotificationRequests() { 1171 return mNativeTabAndroid != 0 1172 && TabImplJni.get().getShouldBlockNewNotificationRequests(mNativeTabAndroid); 1173 } 1174 1175 /** 1176 * Performs any subclass-specific tasks when the Tab crashes. 1177 */ handleTabCrash()1178 void handleTabCrash() { 1179 mIsLoading = false; 1180 1181 RewindableIterator<TabObserver> observers = getTabObservers(); 1182 while (observers.hasNext()) observers.next().onCrash(this); 1183 mIsBeingRestored = false; 1184 } 1185 1186 /** 1187 * Called when the background color for the content changes. 1188 * @param color The current for the background. 1189 */ onBackgroundColorChanged(int color)1190 void onBackgroundColorChanged(int color) { 1191 for (TabObserver observer : mObservers) observer.onBackgroundColorChanged(this, color); 1192 } 1193 1194 /** 1195 * This is currently called when committing a pre-rendered page or activating a portal. 1196 */ 1197 @CalledByNative swapWebContents(WebContents webContents, boolean didStartLoad, boolean didFinishLoad)1198 void swapWebContents(WebContents webContents, boolean didStartLoad, boolean didFinishLoad) { 1199 boolean hasWebContents = mContentView != null && mWebContents != null; 1200 Rect original = hasWebContents 1201 ? new Rect(0, 0, mContentView.getWidth(), mContentView.getHeight()) 1202 : new Rect(); 1203 for (TabObserver observer : mObservers) observer.webContentsWillSwap(this); 1204 if (hasWebContents) mWebContents.onHide(); 1205 Context appContext = ContextUtils.getApplicationContext(); 1206 Rect bounds = original.isEmpty() ? TabUtils.estimateContentSize(appContext) : null; 1207 if (bounds != null) original.set(bounds); 1208 1209 mWebContents.setFocus(false); 1210 destroyWebContents(false /* do not delete native web contents */); 1211 hideNativePage(false, () -> { 1212 // Size of the new content is zero at this point. Set the view size in advance 1213 // so that next onShow() call won't send a resize message with zero size 1214 // to the renderer process. This prevents the size fluttering that may confuse 1215 // Blink and break rendered result (see http://crbug.com/340987). 1216 webContents.setSize(original.width(), original.height()); 1217 1218 if (bounds != null) { 1219 assert mNativeTabAndroid != 0; 1220 TabImplJni.get().onPhysicalBackingSizeChanged( 1221 mNativeTabAndroid, TabImpl.this, webContents, bounds.right, bounds.bottom); 1222 } 1223 webContents.onShow(); 1224 initWebContents(webContents); 1225 }); 1226 1227 if (didStartLoad) { 1228 // Simulate the PAGE_LOAD_STARTED notification that we did not get. 1229 didStartPageLoad(getUrl()); 1230 1231 // Simulate the PAGE_LOAD_FINISHED notification that we did not get. 1232 if (didFinishLoad) didFinishPageLoad(getUrlString()); 1233 } 1234 1235 for (TabObserver observer : mObservers) { 1236 observer.onWebContentsSwapped(this, didStartLoad, didFinishLoad); 1237 } 1238 } 1239 1240 /** 1241 * Builds the native counterpart to this class. 1242 */ initializeNative()1243 private void initializeNative() { 1244 if (mNativeTabAndroid == 0) TabImplJni.get().init(TabImpl.this); 1245 assert mNativeTabAndroid != 0; 1246 } 1247 1248 /** 1249 * @return The native pointer representing the native side of this {@link TabImpl} object. 1250 */ 1251 @CalledByNative getNativePtr()1252 private long getNativePtr() { 1253 return mNativeTabAndroid; 1254 } 1255 1256 @CalledByNative clearNativePtr()1257 private void clearNativePtr() { 1258 assert mNativeTabAndroid != 0; 1259 mNativeTabAndroid = 0; 1260 } 1261 1262 @CalledByNative setNativePtr(long nativePtr)1263 private void setNativePtr(long nativePtr) { 1264 assert nativePtr != 0; 1265 assert mNativeTabAndroid == 0; 1266 mNativeTabAndroid = nativePtr; 1267 } 1268 1269 /** 1270 * Initializes the {@link WebContents}. Completes the browser content components initialization 1271 * around a native WebContents pointer. 1272 * <p> 1273 * {@link #getNativePage()} will still return the {@link NativePage} if there is one. 1274 * All initialization that needs to reoccur after a web contents swap should be added here. 1275 * <p /> 1276 * NOTE: If you attempt to pass a native WebContents that does not have the same incognito 1277 * state as this tab this call will fail. 1278 * 1279 * @param webContents The WebContents object that will initialize all the browser components. 1280 */ initWebContents(WebContents webContents)1281 private void initWebContents(WebContents webContents) { 1282 try { 1283 TraceEvent.begin("ChromeTab.initWebContents"); 1284 WebContents oldWebContents = mWebContents; 1285 mWebContents = webContents; 1286 1287 ContentView cv = ContentView.createContentView( 1288 mThemedApplicationContext, null /* eventOffsetHandler */, webContents); 1289 cv.setContentDescription(mThemedApplicationContext.getResources().getString( 1290 R.string.accessibility_content_view)); 1291 mContentView = cv; 1292 webContents.initialize(PRODUCT_VERSION, new TabViewAndroidDelegate(this, cv), cv, 1293 getWindowAndroid(), WebContents.createDefaultInternalsHolder()); 1294 hideNativePage(false, null); 1295 1296 if (oldWebContents != null) { 1297 oldWebContents.setImportance(ChildProcessImportance.NORMAL); 1298 getWebContentsAccessibility(oldWebContents).setObscuredByAnotherView(false); 1299 } 1300 1301 mWebContents.setImportance(mImportance); 1302 ContentUtils.setUserAgentOverride(mWebContents); 1303 1304 mContentView.addOnAttachStateChangeListener(mAttachStateChangeListener); 1305 updateInteractableState(); 1306 1307 mWebContentsDelegate = createWebContentsDelegate(); 1308 1309 assert mNativeTabAndroid != 0; 1310 TabImplJni.get().initWebContents(mNativeTabAndroid, TabImpl.this, mIncognito, 1311 isDetached(this), webContents, mSourceTabId, mWebContentsDelegate, 1312 new TabContextMenuPopulatorFactory( 1313 mDelegateFactory.createContextMenuPopulatorFactory(this), this)); 1314 1315 mWebContents.notifyRendererPreferenceUpdate(); 1316 TabHelpers.initWebContentsHelpers(this); 1317 notifyContentChanged(); 1318 } finally { 1319 TraceEvent.end("ChromeTab.initWebContents"); 1320 } 1321 } 1322 createWebContentsDelegate()1323 private TabWebContentsDelegateAndroidImpl createWebContentsDelegate() { 1324 TabWebContentsDelegateAndroid delegate = mDelegateFactory.createWebContentsDelegate(this); 1325 return new TabWebContentsDelegateAndroidImpl(this, delegate); 1326 } 1327 1328 /** 1329 * Shows the given {@code nativePage} if it's not already showing. 1330 * @param nativePage The {@link NativePage} to show. 1331 */ showNativePage(NativePage nativePage)1332 private void showNativePage(NativePage nativePage) { 1333 assert nativePage != null; 1334 if (mNativePage == nativePage) return; 1335 hideNativePage(true, () -> { 1336 mNativePage = nativePage; 1337 if (!mNativePage.isFrozen()) { 1338 mNativePage.getView().addOnAttachStateChangeListener(mAttachStateChangeListener); 1339 } 1340 pushNativePageStateToNavigationEntry(); 1341 }); 1342 } 1343 1344 /** 1345 * Hide and destroy the native page if it was being shown. 1346 * @param notify {@code true} to trigger {@link #onContentChanged} event. 1347 * @param postHideTask {@link Runnable} task to run before actually destroying the 1348 * native page. This is necessary to keep the tasks to perform in order. 1349 */ hideNativePage(boolean notify, Runnable postHideTask)1350 private void hideNativePage(boolean notify, Runnable postHideTask) { 1351 NativePage previousNativePage = mNativePage; 1352 if (mNativePage != null) { 1353 if (!mNativePage.isFrozen()) { 1354 mNativePage.getView().removeOnAttachStateChangeListener(mAttachStateChangeListener); 1355 } 1356 mNativePage = null; 1357 } 1358 if (postHideTask != null) postHideTask.run(); 1359 if (notify) notifyContentChanged(); 1360 destroyNativePageInternal(previousNativePage); 1361 } 1362 1363 /** 1364 * Set {@link TabDelegateFactory} instance and updates the references. 1365 * @param factory TabDelegateFactory instance. 1366 */ setDelegateFactory(TabDelegateFactory factory)1367 private void setDelegateFactory(TabDelegateFactory factory) { 1368 // Update the delegate factory, then recreate and propagate all delegates. 1369 mDelegateFactory = factory; 1370 1371 mWebContentsDelegate = createWebContentsDelegate(); 1372 1373 WebContents webContents = getWebContents(); 1374 if (webContents != null) { 1375 TabImplJni.get().updateDelegates(mNativeTabAndroid, TabImpl.this, mWebContentsDelegate, 1376 new TabContextMenuPopulatorFactory( 1377 mDelegateFactory.createContextMenuPopulatorFactory(this), this)); 1378 webContents.notifyRendererPreferenceUpdate(); 1379 } 1380 } 1381 notifyPageTitleChanged()1382 private void notifyPageTitleChanged() { 1383 RewindableIterator<TabObserver> observers = getTabObservers(); 1384 while (observers.hasNext()) { 1385 observers.next().onTitleUpdated(this); 1386 } 1387 } 1388 notifyFaviconChanged()1389 private void notifyFaviconChanged() { 1390 RewindableIterator<TabObserver> observers = getTabObservers(); 1391 while (observers.hasNext()) { 1392 observers.next().onFaviconUpdated(this, null); 1393 } 1394 } 1395 1396 /** 1397 * Update the interactable state of the tab. If the state has changed, it will call the 1398 * {@link #onInteractableStateChanged(boolean)} method. 1399 */ updateInteractableState()1400 private void updateInteractableState() { 1401 boolean currentState = !mIsHidden && !isFrozen() 1402 && (mIsViewAttachedToWindow || VrModuleProvider.getDelegate().isInVr()); 1403 1404 if (currentState == mInteractableState) return; 1405 1406 mInteractableState = currentState; 1407 for (TabObserver observer : mObservers) { 1408 observer.onInteractabilityChanged(this, currentState); 1409 } 1410 } 1411 1412 /** 1413 * Loads a tab that was already loaded but since then was lost. This happens either when we 1414 * unfreeze the tab from serialized state or when we reload a tab that crashed. In both cases 1415 * the load codepath is the same (run in loadIfNecessary()) and the same caching policies of 1416 * history load are used. 1417 */ restoreIfNeeded()1418 private final void restoreIfNeeded() { 1419 // Attempts to display the Paint Preview representation of this Tab. Please note that this 1420 // is behind an experimental flag (crbug.com/1008520). 1421 if (isFrozen()) StartupPaintPreviewHelper.showPaintPreviewOnRestore(this); 1422 1423 try { 1424 TraceEvent.begin("Tab.restoreIfNeeded"); 1425 // Restore is needed for a tab that is loaded for the first time. WebContents will 1426 // be restored from a saved state. 1427 if ((isFrozen() && CriticalPersistedTabData.from(this).getWebContentsState() != null 1428 && !unfreezeContents()) 1429 || !needsReload()) { 1430 return; 1431 } 1432 1433 if (mWebContents != null) mWebContents.getNavigationController().loadIfNecessary(); 1434 mIsBeingRestored = true; 1435 for (TabObserver observer : mObservers) observer.onRestoreStarted(this); 1436 } finally { 1437 TraceEvent.end("Tab.restoreIfNeeded"); 1438 } 1439 } 1440 1441 /** 1442 * Restores the WebContents from its saved state. This should only be called if the tab is 1443 * frozen with a saved TabState, and NOT if it was frozen for a lazy load. 1444 * @return Whether or not the restoration was successful. 1445 */ unfreezeContents()1446 private boolean unfreezeContents() { 1447 boolean restored = true; 1448 try { 1449 TraceEvent.begin("Tab.unfreezeContents"); 1450 WebContentsState webContentsState = 1451 CriticalPersistedTabData.from(this).getWebContentsState(); 1452 assert webContentsState != null; 1453 1454 WebContents webContents = WebContentsStateBridge.restoreContentsFromByteBuffer( 1455 webContentsState, isHidden()); 1456 if (webContents == null) { 1457 // State restore failed, just create a new empty web contents as that is the best 1458 // that can be done at this point. TODO(jcivelli) http://b/5910521 - we should show 1459 // an error page instead of a blank page in that case (and the last loaded URL). 1460 webContents = WebContentsFactory.createWebContents(isIncognito(), isHidden()); 1461 for (TabObserver observer : mObservers) observer.onRestoreFailed(this); 1462 restored = false; 1463 } 1464 View compositorView = getActivity().getCompositorViewHolder(); 1465 webContents.setSize(compositorView.getWidth(), compositorView.getHeight()); 1466 1467 CriticalPersistedTabData.from(this).setWebContentsState(null); 1468 initWebContents(webContents); 1469 1470 if (!restored) { 1471 String url = CriticalPersistedTabData.from(this).getUrl().getSpec().isEmpty() 1472 ? UrlConstants.NTP_URL 1473 : CriticalPersistedTabData.from(this).getUrl().getSpec(); 1474 loadUrl(new LoadUrlParams(url, PageTransition.GENERATED)); 1475 } 1476 } finally { 1477 TraceEvent.end("Tab.unfreezeContents"); 1478 } 1479 return restored; 1480 } 1481 1482 @CalledByNative isCustomTab()1483 private boolean isCustomTab() { 1484 ChromeActivity activity = getActivity(); 1485 return activity != null && activity.isCustomTab(); 1486 } 1487 1488 /** 1489 * Throws a RuntimeException. Useful for testing crash reports with obfuscated Java stacktraces. 1490 */ handleJavaCrash()1491 private int handleJavaCrash() { 1492 throw new RuntimeException("Intentional Java Crash"); 1493 } 1494 1495 /** 1496 * Delete navigation entries from frozen state matching the predicate. 1497 * @param predicate Handle for a deletion predicate interpreted by native code. 1498 * Only valid during this call frame. 1499 */ 1500 @CalledByNative deleteNavigationEntriesFromFrozenState(long predicate)1501 private void deleteNavigationEntriesFromFrozenState(long predicate) { 1502 WebContentsState webContentsState = 1503 CriticalPersistedTabData.from(this).getWebContentsState(); 1504 if (webContentsState == null) return; 1505 WebContentsState newState = 1506 WebContentsStateBridge.deleteNavigationEntries(webContentsState, predicate); 1507 if (newState != null) { 1508 CriticalPersistedTabData.from(this).setWebContentsState(newState); 1509 notifyNavigationEntriesDeleted(); 1510 } 1511 } 1512 getWebContentsAccessibility(WebContents webContents)1513 private static WebContentsAccessibility getWebContentsAccessibility(WebContents webContents) { 1514 return webContents != null ? WebContentsAccessibility.fromWebContents(webContents) : null; 1515 } 1516 destroyNativePageInternal(NativePage nativePage)1517 private void destroyNativePageInternal(NativePage nativePage) { 1518 if (nativePage == null) return; 1519 assert nativePage != mNativePage : "Attempting to destroy active page."; 1520 1521 nativePage.destroy(); 1522 } 1523 1524 /** 1525 * Destroys the current {@link WebContents}. 1526 * @param deleteNativeWebContents Whether or not to delete the native WebContents pointer. 1527 */ destroyWebContents(boolean deleteNativeWebContents)1528 private final void destroyWebContents(boolean deleteNativeWebContents) { 1529 if (mWebContents == null) return; 1530 1531 mContentView.removeOnAttachStateChangeListener(mAttachStateChangeListener); 1532 mContentView = null; 1533 updateInteractableState(); 1534 1535 WebContents contentsToDestroy = mWebContents; 1536 mWebContents = null; 1537 mWebContentsDelegate = null; 1538 1539 assert mNativeTabAndroid != 0; 1540 if (deleteNativeWebContents) { 1541 // Destruction of the native WebContents will call back into Java to destroy the Java 1542 // WebContents. 1543 TabImplJni.get().destroyWebContents(mNativeTabAndroid, TabImpl.this); 1544 } else { 1545 TabImplJni.get().releaseWebContents(mNativeTabAndroid, TabImpl.this); 1546 // Since the native WebContents is still alive, we need to clear its reference to the 1547 // Java WebContents. While doing so, it will also call back into Java to destroy the 1548 // Java WebContents. 1549 contentsToDestroy.clearNativeReference(); 1550 } 1551 } 1552 1553 @NativeMethods 1554 interface Natives { fromWebContents(WebContents webContents)1555 TabImpl fromWebContents(WebContents webContents); init(TabImpl caller)1556 void init(TabImpl caller); destroy(long nativeTabAndroid, TabImpl caller)1557 void destroy(long nativeTabAndroid, TabImpl caller); initWebContents(long nativeTabAndroid, TabImpl caller, boolean incognito, boolean isBackgroundTab, WebContents webContents, int parentTabId, TabWebContentsDelegateAndroidImpl delegate, ContextMenuPopulatorFactory contextMenuPopulatorFactory)1558 void initWebContents(long nativeTabAndroid, TabImpl caller, boolean incognito, 1559 boolean isBackgroundTab, WebContents webContents, int parentTabId, 1560 TabWebContentsDelegateAndroidImpl delegate, 1561 ContextMenuPopulatorFactory contextMenuPopulatorFactory); updateDelegates(long nativeTabAndroid, TabImpl caller, TabWebContentsDelegateAndroidImpl delegate, ContextMenuPopulatorFactory contextMenuPopulatorFactory)1562 void updateDelegates(long nativeTabAndroid, TabImpl caller, 1563 TabWebContentsDelegateAndroidImpl delegate, 1564 ContextMenuPopulatorFactory contextMenuPopulatorFactory); destroyWebContents(long nativeTabAndroid, TabImpl caller)1565 void destroyWebContents(long nativeTabAndroid, TabImpl caller); releaseWebContents(long nativeTabAndroid, TabImpl caller)1566 void releaseWebContents(long nativeTabAndroid, TabImpl caller); onPhysicalBackingSizeChanged(long nativeTabAndroid, TabImpl caller, WebContents webContents, int width, int height)1567 void onPhysicalBackingSizeChanged(long nativeTabAndroid, TabImpl caller, 1568 WebContents webContents, int width, int height); loadUrl(long nativeTabAndroid, TabImpl caller, String url, Origin initiatorOrigin, String extraHeaders, ResourceRequestBody postData, int transition, String referrerUrl, int referrerPolicy, boolean isRendererInitiated, boolean shoulReplaceCurrentEntry, boolean hasUserGesture, boolean shouldClearHistoryList, long inputStartTimestamp, long intentReceivedTimestamp)1569 int loadUrl(long nativeTabAndroid, TabImpl caller, String url, Origin initiatorOrigin, 1570 String extraHeaders, ResourceRequestBody postData, int transition, 1571 String referrerUrl, int referrerPolicy, boolean isRendererInitiated, 1572 boolean shoulReplaceCurrentEntry, boolean hasUserGesture, 1573 boolean shouldClearHistoryList, long inputStartTimestamp, 1574 long intentReceivedTimestamp); setActiveNavigationEntryTitleForUrl( long nativeTabAndroid, TabImpl caller, String url, String title)1575 void setActiveNavigationEntryTitleForUrl( 1576 long nativeTabAndroid, TabImpl caller, String url, String title); loadOriginalImage(long nativeTabAndroid, TabImpl caller)1577 void loadOriginalImage(long nativeTabAndroid, TabImpl caller); setAddApi2TransitionToFutureNavigations(long nativeTabAndroid, boolean shouldAdd)1578 void setAddApi2TransitionToFutureNavigations(long nativeTabAndroid, boolean shouldAdd); getAddApi2TransitionToFutureNavigations(long nativeTabAndroid)1579 boolean getAddApi2TransitionToFutureNavigations(long nativeTabAndroid); setHideFutureNavigations(long nativeTabAndroid, boolean hide)1580 void setHideFutureNavigations(long nativeTabAndroid, boolean hide); getHideFutureNavigations(long nativeTabAndroid)1581 boolean getHideFutureNavigations(long nativeTabAndroid); setShouldBlockNewNotificationRequests(long nativeTabAndroid, boolean value)1582 void setShouldBlockNewNotificationRequests(long nativeTabAndroid, boolean value); getShouldBlockNewNotificationRequests(long nativeTabAndroid)1583 boolean getShouldBlockNewNotificationRequests(long nativeTabAndroid); 1584 } 1585 } 1586