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