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