• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.ui.base.Clipboard;
34 import org.chromium.ui.base.WindowAndroid;
35 
36 import 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  */
49 public 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          */
TabBaseChromeContextMenuItemDelegate()115         public TabBaseChromeContextMenuItemDelegate() {
116             mClipboard = new Clipboard(getApplicationContext());
117         }
118 
119         @Override
isIncognito()120         public boolean isIncognito() {
121             return mIncognito;
122         }
123 
124         @Override
onSaveToClipboard(String text, boolean isUrl)125         public void onSaveToClipboard(String text, boolean isUrl) {
126             mClipboard.setText(text, text);
127         }
128 
129         @Override
onSaveImageToClipboard(String url)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
onLoadProgressChanged(int progress)142         public void onLoadProgressChanged(int progress) {
143             for (TabObserver observer : mObservers) {
144                 observer.onLoadProgressChanged(TabBase.this, progress);
145             }
146         }
147 
148         @Override
onUpdateUrl(String url)149         public void onUpdateUrl(String url) {
150             for (TabObserver observer : mObservers) observer.onUpdateUrl(TabBase.this, url);
151         }
152 
153         @Override
showRepostFormWarningDialog(final ContentViewCore contentViewCore)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
toggleFullscreenModeForTab(boolean enableFullscreen)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 {
TabBaseContextMenuPopulator(ContextMenuPopulator populator)180         public TabBaseContextMenuPopulator(ContextMenuPopulator populator) {
181             super(populator);
182         }
183 
184         @Override
buildContextMenu(ContextMenu menu, Context context, ContextMenuParams params)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 {
TabBaseWebContentsObserverAndroid(ContentViewCore contentViewCore)192         public TabBaseWebContentsObserverAndroid(ContentViewCore contentViewCore) {
193             super(contentViewCore);
194         }
195 
196         @Override
navigationEntryCommitted()197         public void navigationEntryCommitted() {
198             if (getNativePage() != null) {
199                 pushNativePageStateToNavigationEntry();
200             }
201         }
202 
203         @Override
didFailLoad(boolean isProvisionalLoad, boolean isMainFrame, int errorCode, String description, String failingUrl)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      */
TabBase(boolean incognito, Context context, WindowAndroid window)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      */
TabBase(int id, boolean incognito, Context context, WindowAndroid window)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      */
addObserver(TabObserver observer)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      */
removeObserver(TabObserver observer)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      */
canGoBack()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      */
canGoForward()268     public boolean canGoForward() {
269         return mContentViewCore != null && mContentViewCore.canGoForward();
270     }
271 
272     /**
273      * Goes to the navigation entry before the current one.
274      */
goBack()275     public void goBack() {
276         if (mContentViewCore != null) mContentViewCore.goBack();
277     }
278 
279     /**
280      * Goes to the navigation entry after the current one.
281      */
goForward()282     public void goForward() {
283         if (mContentViewCore != null) mContentViewCore.goForward();
284     }
285 
286     @Override
getDirectedNavigationHistory(boolean isForward, int itemLimit)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
goToNavigationIndex(int index)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      */
loadIfNecessary()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      */
requestRestoreLoad()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      */
isShowingInterstitialPage()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      */
isReady()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      */
getView()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      */
getWidth()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      */
getHeight()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      */
getApplicationContext()359     protected Context getApplicationContext() {
360         return mApplicationContext;
361     }
362 
363     /**
364      * @return The infobar container.
365      */
getInfoBarContainer()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      */
createAutoLoginProcessor()374     protected abstract AutoLoginProcessor createAutoLoginProcessor();
375 
376     /**
377      * Prints the current page.
378      *
379      * @return Whether the printing process is started successfully.
380      **/
print()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      */
reload()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      */
reloadIgnoringCache()398     public void reloadIgnoringCache() {
399         if (mContentViewCore != null) mContentViewCore.reloadIgnoringCache(true);
400     }
401 
402     /** Stop the current navigation. */
stopLoading()403     public void stopLoading() {
404         if (mContentViewCore != null) mContentViewCore.stopLoading();
405     }
406 
407     /**
408      * @return The background color of the tab.
409      */
getBackgroundColor()410     public int getBackgroundColor() {
411         return getPageInfo() != null ? getPageInfo().getBackgroundColor() : Color.WHITE;
412     }
413 
414     /**
415      * @return The profile associated with this tab.
416      */
getProfile()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
getId()426     public int getId() {
427         return mId;
428     }
429 
430     /**
431      * @return Whether or not this tab is incognito.
432      */
isIncognito()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      */
getContentView()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      */
getContentViewCore()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      */
getPageInfo()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      */
getNativePage()468     public NativePage getNativePage() {
469         return mNativePage;
470     }
471 
472     /**
473      * @return Whether or not the {@link TabBase} represents a {@link NativePage}.
474      */
isNativePage()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      */
setUseDesktopUserAgent(boolean useDesktop, boolean reloadOnChange)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      */
getUseDesktopUserAgent()494     public boolean getUseDesktopUserAgent() {
495         return mContentViewCore != null && mContentViewCore.getUseDesktopUserAgent();
496     }
497 
498     /**
499      * @return The current {ToolbarModelSecurityLevel} for the tab.
500      */
getSecurityLevel()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
getSyncId()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
setSyncId(int syncId)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      */
getTabObservers()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      */
getContentViewClient()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      */
setContentViewClient(ContentViewClient client)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      */
showNativePage(NativePage nativePage)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      */
showRenderedPage()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      */
initialize()588     public void initialize() { }
589 
590     /**
591      * A helper method to initialize a {@link ContentView} without any native WebContents pointer.
592      */
initContentView()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      */
initContentView(long nativeWebContents)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      */
destroy()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      */
isInitialized()659     public boolean isInitialized() {
660         return mNativeTabAndroid != 0;
661     }
662 
663     /**
664      * @return The url associated with the tab.
665      */
666     @CalledByNative
getUrl()667     public String getUrl() {
668         return mContentView != null ? mContentView.getUrl() : "";
669     }
670 
671     /**
672      * @return The tab title.
673      */
674     @CalledByNative
getTitle()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      */
getFavicon()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
restoreIfNeeded()693     public boolean restoreIfNeeded() {
694         return false;
695     }
696 
destroyNativePageInternal(NativePage nativePage)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      */
destroyContentView(boolean deleteNativeWebContents)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      */
destroyContentViewInternal(ContentView contentView)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      */
createWebContentsDelegate()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      */
createContextMenuPopulator()748     protected ContextMenuPopulator createContextMenuPopulator() {
749         return new ChromeContextMenuPopulator(new TabBaseChromeContextMenuItemDelegate());
750     }
751 
752     /**
753      * @return The {@link WindowAndroid} associated with this {@link TabBase}.
754      */
getWindowAndroid()755     protected WindowAndroid getWindowAndroid() {
756         return mWindowAndroid;
757     }
758 
759     /**
760      * @return The current {@link TabBaseChromeWebContentsDelegateAndroid} instance.
761      */
getChromeWebContentsDelegateAndroid()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
onFaviconUpdated()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
getNativePtr()778     protected long getNativePtr() {
779         return mNativeTabAndroid;
780     }
781 
782     /** This is currently called when committing a pre-rendered page. */
783     @CalledByNative
swapWebContents(final long newWebContents)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
clearNativePtr()798     private void clearNativePtr() {
799         assert mNativeTabAndroid != 0;
800         mNativeTabAndroid = 0;
801     }
802 
803     @CalledByNative
setNativePtr(long nativePtr)804     private void setNativePtr(long nativePtr) {
805         assert mNativeTabAndroid == 0;
806         mNativeTabAndroid = nativePtr;
807     }
808 
809     @CalledByNative
getNativeInfoBarContainer()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      */
generateValidId(int id)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      */
generateNextId()830     private static int generateNextId() {
831         return sIdCounter.getAndIncrement();
832     }
833 
pushNativePageStateToNavigationEntry()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      */
incrementIdCounterTo(int id)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 
nativeInitWebContents(long nativeTabAndroid, boolean incognito, ContentViewCore contentViewCore, ChromeWebContentsDelegateAndroid delegate, ContextMenuPopulator contextMenuPopulator)856     private native void nativeInitWebContents(long nativeTabAndroid, boolean incognito,
857             ContentViewCore contentViewCore, ChromeWebContentsDelegateAndroid delegate,
858             ContextMenuPopulator contextMenuPopulator);
nativeDestroyWebContents(long nativeTabAndroid, boolean deleteNative)859     private native void nativeDestroyWebContents(long nativeTabAndroid, boolean deleteNative);
nativeGetProfileAndroid(long nativeTabAndroid)860     private native Profile nativeGetProfileAndroid(long nativeTabAndroid);
nativeGetSecurityLevel(long nativeTabAndroid)861     private native int nativeGetSecurityLevel(long nativeTabAndroid);
nativeSetActiveNavigationEntryTitleForUrl(long nativeTabAndroid, String url, String title)862     private native void nativeSetActiveNavigationEntryTitleForUrl(long nativeTabAndroid, String url,
863             String title);
nativePrint(long nativeTabAndroid)864     private native boolean nativePrint(long nativeTabAndroid);
865 }
866