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