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