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