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