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