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