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