Index: chrome/android/java/src/org/chromium/chrome/browser/TabBase.java |
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/TabBase.java b/chrome/android/java/src/org/chromium/chrome/browser/TabBase.java |
index 7f73cac91644550d0126bbe2401d67cde1a6c9ab..6dbd1bca48a9f2fa7c8867ff989e4007a0558d6c 100644 |
--- a/chrome/android/java/src/org/chromium/chrome/browser/TabBase.java |
+++ b/chrome/android/java/src/org/chromium/chrome/browser/TabBase.java |
@@ -4,10 +4,24 @@ |
package org.chromium.chrome.browser; |
+import android.content.Context; |
+import android.graphics.Color; |
+import android.view.View; |
+ |
import org.chromium.base.CalledByNative; |
+import org.chromium.base.ObserverList; |
+import org.chromium.chrome.browser.profiles.Profile; |
import org.chromium.content.browser.ContentView; |
+import org.chromium.content.browser.ContentViewClient; |
+import org.chromium.content.browser.ContentViewCore; |
+import org.chromium.content.browser.NavigationClient; |
+import org.chromium.content.browser.NavigationHistory; |
+import org.chromium.content.browser.PageInfo; |
+import org.chromium.content.browser.WebContentsObserverAndroid; |
import org.chromium.ui.WindowAndroid; |
+import java.util.concurrent.atomic.AtomicInteger; |
+ |
/** |
* The basic Java representation of a tab. Contains and manages a {@link ContentView}. |
* |
@@ -19,18 +33,478 @@ import org.chromium.ui.WindowAndroid; |
* destroyed which will call into the native subclass and finally lead to the destruction of the |
* parent classes. |
*/ |
-public abstract class TabBase { |
+public abstract class TabBase implements NavigationClient { |
public static final int INVALID_TAB_ID = -1; |
- private final WindowAndroid mWindowAndroid; |
+ /** Used for automatically generating tab ids. */ |
+ private static final AtomicInteger sIdCounter = new AtomicInteger(); |
+ |
private int mNativeTabAndroid; |
- protected TabBase(WindowAndroid window) { |
+ /** Unique id of this tab (within its container). */ |
+ private final int mId; |
+ |
+ /** Whether or not this tab is an incognito tab. */ |
+ private final boolean mIncognito; |
+ |
+ /** The {@link Context} used to create {@link View}s or other Android components. This is |
+ * purposely an application Context not an activity Context. */ |
+ private final Context mContext; |
+ |
+ /** Gives {@link TabBase} a way to interact with the Android window. */ |
+ private final WindowAndroid mWindowAndroid; |
+ |
+ /** The current native page (e.g. chrome-native://newtab), or {@code null} if there is none. */ |
+ private NativePage mNativePage; |
+ |
+ /** The {@link ContentView} showing the current page or {@code null} if the tab is frozen. */ |
+ private ContentView mContentView; |
+ |
+ /** |
+ * The {@link ContentViewCore} for the current page, provided for convenience. This always |
+ * equals {@link ContentView#getContentViewCore()}, or {@code null} if mContentView is |
+ * {@code null}. |
+ */ |
+ private ContentViewCore mContentViewCore; |
+ |
+ // Observers and Delegates. |
+ private ContentViewClient mContentViewClient; |
+ private WebContentsObserverAndroid mWebContentsObserver; |
+ private TabBaseChromeWebContentsDelegateAndroid mWebContentsDelegate; |
+ private ObserverList<TabObserver> mObservers = new ObserverList<TabObserver>(); |
+ |
+ /** |
+ * A basic {@link ChromeWebContentsDelegateAndroid} that forwards some calls to the registered |
+ * {@link TabObserver}s. Meant to be overridden by subclasses. |
+ */ |
+ public class TabBaseChromeWebContentsDelegateAndroid |
+ extends ChromeWebContentsDelegateAndroid { |
+ @Override |
+ public void onLoadProgressChanged(int progress) { |
+ for (TabObserver observer : mObservers) { |
+ observer.onLoadProgressChanged(TabBase.this, progress); |
+ } |
+ } |
+ |
+ @Override |
+ public void onUpdateUrl(String url) { |
+ for (TabObserver observer : mObservers) observer.onUpdateUrl(TabBase.this, url); |
+ } |
+ } |
+ |
+ /** |
+ * Creates an instance of a {@link TabBase} with no id. |
+ * @param incognito Whether or not this tab is incognito. |
+ * @param context An instance of a {@link Context}. |
+ * @param window An instance of a {@link WindowAndroid}. |
+ */ |
+ public TabBase(boolean incognito, Context context, WindowAndroid window) { |
+ this(INVALID_TAB_ID, incognito, context, window); |
+ } |
+ |
+ /** |
+ * Creates an instance of a {@link TabBase}. |
+ * @param id The id this tab should be identified with. |
+ * @param incognito Whether or not this tab is incognito. |
+ * @param context An instance of a {@link Context}. |
+ * @param window An instance of a {@link WindowAndroid}. |
+ */ |
+ public TabBase(int id, boolean incognito, Context context, WindowAndroid window) { |
+ mId = generateValidId(id); |
+ mIncognito = incognito; |
+ mContext = context != null ? context.getApplicationContext() : null; |
mWindowAndroid = window; |
} |
+ /** |
+ * Adds a {@link TabObserver} to be notified on {@link TabBase} changes. |
+ * @param observer The {@link TabObserver} to add. |
+ */ |
+ public final void addObserver(TabObserver observer) { |
+ mObservers.addObserver(observer); |
+ } |
+ |
+ /** |
+ * Removes a {@link TabObserver}. |
+ * @param observer The {@link TabObserver} to remove. |
+ */ |
+ public final void removeObserver(TabObserver observer) { |
+ mObservers.removeObserver(observer); |
+ } |
+ |
+ /** |
+ * @return Whether or not this tab has a previous navigation entry. |
+ */ |
+ public boolean canGoBack() { |
+ return mContentViewCore != null && mContentViewCore.canGoBack(); |
+ } |
+ |
+ /** |
+ * @return Whether or not this tab has a navigation entry after the current one. |
+ */ |
+ public boolean canGoForward() { |
+ return mContentViewCore != null && mContentViewCore.canGoForward(); |
+ } |
+ |
+ /** |
+ * Goes to the navigation entry before the current one. |
+ */ |
+ public void goBack() { |
+ if (mContentViewCore != null) mContentViewCore.goBack(); |
+ } |
+ |
+ /** |
+ * Goes to the navigation entry after the current one. |
+ */ |
+ public void goForward() { |
+ if (mContentViewCore != null) mContentViewCore.goForward(); |
+ } |
+ |
+ @Override |
+ public NavigationHistory getDirectedNavigationHistory(boolean isForward, int itemLimit) { |
+ if (mContentViewCore != null) { |
+ return mContentViewCore.getDirectedNavigationHistory(isForward, itemLimit); |
+ } else { |
+ return new NavigationHistory(); |
+ } |
+ } |
+ |
+ @Override |
+ public void goToNavigationIndex(int index) { |
+ if (mContentViewCore != null) mContentViewCore.goToNavigationIndex(index); |
+ } |
+ |
+ /** |
+ * @return Whether or not the {@link TabBase} is currently showing an interstitial page, such as |
+ * a bad HTTPS page. |
+ */ |
+ public boolean isShowingInterstitialPage() { |
+ ContentViewCore contentViewCore = getContentViewCore(); |
+ return contentViewCore != null && contentViewCore.isShowingInterstitialPage(); |
+ } |
+ |
+ /** |
+ * @return Whether or not the tab has something valid to render. |
+ */ |
+ public boolean isReady() { |
+ return mNativePage != null || (mContentViewCore != null && mContentViewCore.isReady()); |
+ } |
+ |
+ /** |
+ * @return The {@link View} displaying the current page in the tab. This might be a |
+ * {@link ContentView} but could potentially be any instance of {@link View}. This can |
+ * be {@code null}, if the tab is frozen or being initialized or destroyed. |
+ */ |
+ public View getView() { |
+ PageInfo pageInfo = getPageInfo(); |
+ return pageInfo != null ? pageInfo.getView() : null; |
+ } |
+ |
+ /** |
+ * @return The width of the content of this tab. Can be 0 if there is no content. |
+ */ |
+ public int getWidth() { |
+ View view = getView(); |
+ return view != null ? view.getWidth() : 0; |
+ } |
+ |
+ /** |
+ * @return The height of the content of this tab. Can be 0 if there is no content. |
+ */ |
+ public int getHeight() { |
+ View view = getView(); |
+ return view != null ? view.getHeight() : 0; |
+ } |
+ |
+ /** |
+ * @return The application {@link Context} associated with this tab. |
+ */ |
+ protected Context getApplicationContext() { |
+ return mContext; |
+ } |
+ |
+ /** |
+ * Reloads the current page content if it is a {@link ContentView}. |
+ */ |
+ public void reload() { |
+ // TODO(dtrainor): Should we try to rebuild the ContentView if it's frozen? |
+ ContentViewCore contentViewCore = getContentViewCore(); |
+ if (contentViewCore != null) contentViewCore.reload(); |
+ } |
+ |
+ /** |
+ * @return The background color of the tab. |
+ */ |
+ public int getBackgroundColor() { |
+ return getPageInfo() != null ? getPageInfo().getBackgroundColor() : Color.WHITE; |
+ } |
+ |
+ /** |
+ * @return The profile associated with this tab. |
+ */ |
+ public Profile getProfile() { |
+ if (mNativeTabAndroid == 0) return null; |
+ return nativeGetProfileAndroid(mNativeTabAndroid); |
+ } |
+ |
+ /** |
+ * @return The id representing this tab. |
+ */ |
+ public int getId() { |
+ return mId; |
+ } |
+ |
+ /** |
+ * @return Whether or not this tab is incognito. |
+ */ |
+ public boolean isIncognito() { |
+ return mIncognito; |
+ } |
+ |
+ /** |
+ * @return The {@link ContentView} associated with the current page, or {@code null} if |
+ * there is no current page or the current page is displayed using something besides a |
+ * {@link ContentView}. |
+ */ |
+ public ContentView getContentView() { |
+ return mNativePage == null ? mContentView : null; |
+ } |
+ |
+ /** |
+ * @return The {@link ContentViewCore} associated with the current page, or {@code null} if |
+ * there is no current page or the current page is displayed using something besides a |
+ * {@link ContentView}. |
+ */ |
+ public ContentViewCore getContentViewCore() { |
+ return mNativePage == null ? mContentViewCore : null; |
+ } |
+ |
+ /** |
+ * @return A {@link PageInfo} describing the current page. This is always not {@code null} |
+ * except during initialization, destruction, and when the tab is frozen. |
+ */ |
+ public PageInfo getPageInfo() { |
+ return mNativePage != null ? mNativePage : mContentView; |
+ } |
+ |
+ /** |
+ * @return The {@link NativePage} associated with the current page, or {@code null} if there is |
+ * no current page or the current page is displayed using something besides |
+ * {@link NativePage}. |
+ */ |
+ public NativePage getNativePage() { |
+ return mNativePage; |
+ } |
+ |
+ /** |
+ * @return Whether or not the {@link TabBase} represents a {@link NativePage}. |
+ */ |
+ public boolean isNativePage() { |
+ return mNativePage != null; |
+ } |
+ |
+ /** |
+ * Set whether or not the {@link ContentViewCore} should be using a desktop user agent for the |
+ * currently loaded page. |
+ * @param useDesktop If {@code true}, use a desktop user agent. Otherwise use a mobile one. |
+ * @param reloadOnChange Reload the page if the user agent has changed. |
+ */ |
+ public void setUseDesktopUserAgent(boolean useDesktop, boolean reloadOnChange) { |
+ if (mContentViewCore != null) { |
+ mContentViewCore.setUseDesktopUserAgent(useDesktop, reloadOnChange); |
+ } |
+ } |
+ |
+ /** |
+ * @return Whether or not the {@link ContentViewCore} is using a desktop user agent. |
+ */ |
+ public boolean getUseDesktopUserAgent() { |
+ return mContentViewCore != null && mContentViewCore.getUseDesktopUserAgent(); |
+ } |
+ |
+ /** |
+ * @return The {@link ContentViewClient} currently bound to any {@link ContentViewCore} |
+ * associated with the current page. There can still be a {@link ContentViewClient} |
+ * even when there is no {@link ContentViewCore}. |
+ */ |
+ protected ContentViewClient getContentViewClient() { |
+ return mContentViewClient; |
+ } |
+ |
+ /** |
+ * @param client The {@link ContentViewClient} to be bound to any current or new |
+ * {@link ContentViewCore}s associated with this {@link TabBase}. |
+ */ |
+ protected void setContentViewClient(ContentViewClient client) { |
+ if (mContentViewClient == client) return; |
+ |
+ ContentViewClient oldClient = mContentViewClient; |
+ mContentViewClient = client; |
+ |
+ if (mContentViewCore == null) return; |
+ |
+ if (mContentViewClient != null) { |
+ mContentViewCore.setContentViewClient(mContentViewClient); |
+ } else if (oldClient != null) { |
+ // We can't set a null client, but we should clear references to the last one. |
+ mContentViewCore.setContentViewClient(new ContentViewClient()); |
+ } |
+ } |
+ |
+ /** |
+ * Shows the given {@code nativePage} if it's not already showing. |
+ * @param nativePage The {@link NativePage} to show. |
+ */ |
+ protected void showNativePage(NativePage nativePage) { |
+ if (mNativePage == nativePage) return; |
+ destroyNativePageInternal(); |
+ mNativePage = nativePage; |
+ for (TabObserver observer : mObservers) observer.onContentChanged(this); |
+ } |
+ |
+ /** |
+ * Hides the current {@link NativePage}, if any, and shows the {@link ContentView}. |
+ */ |
+ protected void showRenderedPage() { |
+ if (mNativePage == null) return; |
+ destroyNativePageInternal(); |
+ for (TabObserver observer : mObservers) observer.onContentChanged(this); |
+ } |
+ |
+ /** |
+ * Initializes this {@link TabBase}. |
+ */ |
+ public void initialize() { } |
+ |
+ /** |
+ * A helper method to initialize a {@link ContentView} without any native WebContents pointer. |
+ */ |
+ protected final void initContentView() { |
+ initContentView(ContentViewUtil.createNativeWebContents(mIncognito)); |
+ } |
+ |
+ /** |
+ * Completes the {@link ContentView} specific initialization around a native WebContents |
+ * pointer. {@link #getPageInfo()} will still return the {@link NativePage} if there is one. |
+ * All initialization that needs to reoccur after a web contents swap should be added here. |
+ * <p /> |
+ * NOTE: If you attempt to pass a native WebContents that does not have the same incognito |
+ * state as this tab this call will fail. |
+ * |
+ * @param nativeWebContents The native web contents pointer. |
+ */ |
+ protected void initContentView(int nativeWebContents) { |
+ destroyNativePageInternal(); |
+ |
+ mContentView = ContentView.newInstance(mContext, nativeWebContents, getWindowAndroid()); |
+ |
+ mContentViewCore = mContentView.getContentViewCore(); |
+ mWebContentsDelegate = createWebContentsDelegate(); |
+ mWebContentsObserver = createWebContentsObserverAndroid(mContentViewCore); |
+ |
+ if (mContentViewClient != null) mContentViewCore.setContentViewClient(mContentViewClient); |
+ nativeInitWebContents( |
+ mNativeTabAndroid, mId, mIncognito, mContentViewCore, mWebContentsDelegate); |
+ } |
+ |
+ /** |
+ * Cleans up all internal state, destroying any {@link NativePage} or {@link ContentView} |
+ * currently associated with this {@link TabBase}. Typically, pnce this call is made this |
+ * {@link TabBase} should no longer be used as subclasses usually destroy the native component. |
+ */ |
+ public void destroy() { |
+ for (TabObserver observer : mObservers) observer.onDestroyed(this); |
+ |
+ destroyNativePageInternal(); |
+ destroyContentView(true); |
+ } |
+ |
+ private void destroyNativePageInternal() { |
+ if (mNativePage == null) return; |
+ |
+ mNativePage.destroy(); |
+ mNativePage = null; |
+ } |
+ |
+ /** |
+ * Destroys the current {@link ContentView}. |
+ * @param deleteNativeWebContents Whether or not to delete the native WebContents pointer. |
+ */ |
+ protected final void destroyContentView(boolean deleteNativeWebContents) { |
+ if (mContentView == null) return; |
+ |
+ destroyContentViewInternal(mContentView); |
+ |
+ if (mContentViewCore != null) mContentViewCore.destroy(); |
+ |
+ mContentView = null; |
+ mContentViewCore = null; |
+ mContentViewClient = null; |
+ mWebContentsObserver = null; |
+ nativeDestroyWebContents(mNativeTabAndroid, deleteNativeWebContents); |
+ } |
+ |
+ /** |
+ * Gives subclasses the chance to clean up some state associated with this {@link ContentView}. |
+ * This is because {@link #getContentView()} can return {@code null} if a {@link NativePage} |
+ * is showing. |
+ * @param contentView The {@link ContentView} that should have associated state cleaned up. |
+ */ |
+ protected void destroyContentViewInternal(ContentView contentView) { |
+ } |
+ |
+ /** |
+ * A helper method to allow subclasses to build their own delegate. |
+ * @return An instance of a {@link TabBaseChromeWebContentsDelegateAndroid}. |
+ */ |
+ protected TabBaseChromeWebContentsDelegateAndroid createWebContentsDelegate() { |
+ return new TabBaseChromeWebContentsDelegateAndroid(); |
+ } |
+ |
+ /** |
+ * A helper method to allow subclasses to build their own observer. |
+ * @param contentViewCore The {@link ContentViewCore} this observer should be built for. |
+ * @return An instance of a {@link WebContentsObserverAndroid}. |
+ */ |
+ protected WebContentsObserverAndroid createWebContentsObserverAndroid( |
+ ContentViewCore contentViewCore) { |
+ return null; |
+ } |
+ |
+ /** |
+ * @return The {@link WindowAndroid} associated with this {@link TabBase}. |
+ */ |
+ protected WindowAndroid getWindowAndroid() { |
+ return mWindowAndroid; |
+ } |
+ |
+ /** |
+ * @return The current {@link TabBaseChromeWebContentsDelegateAndroid} instance. |
+ */ |
+ protected TabBaseChromeWebContentsDelegateAndroid getChromeWebContentsDelegateAndroid() { |
+ return mWebContentsDelegate; |
+ } |
+ |
+ /** |
+ * @return The native pointer representing the native side of this {@link TabBase} object. |
+ */ |
@CalledByNative |
- private void destroyBase() { |
+ protected int getNativePtr() { |
+ return mNativeTabAndroid; |
+ } |
+ |
+ /** This is currently called when committing a pre-rendered page. */ |
+ @CalledByNative |
+ private void swapWebContents(final int newWebContents) { |
+ destroyContentView(false); |
+ initContentView(newWebContents); |
+ |
+ mContentViewCore.onShow(); |
+ for (TabObserver observer : mObservers) observer.onContentChanged(this); |
+ } |
+ |
+ @CalledByNative |
+ private void clearNativePtr() { |
assert mNativeTabAndroid != 0; |
mNativeTabAndroid = 0; |
} |
@@ -41,11 +515,44 @@ public abstract class TabBase { |
mNativeTabAndroid = nativePtr; |
} |
- int getNativePtr() { |
- return mNativeTabAndroid; |
+ /** |
+ * Validates {@code id} and increments the internal counter to make sure future ids don't |
+ * collide. |
+ * @param id The current id. Maybe {@link #INVALID_TAB_ID}. |
+ * @return A new id if {@code id} was {@link #INVALID_TAB_ID}, or {@code id}. |
+ */ |
+ private static int generateValidId(int id) { |
+ if (id == INVALID_TAB_ID) id = generateNextId(); |
+ incrementIdCounterTo(id + 1); |
+ |
+ return id; |
} |
- protected WindowAndroid getWindowAndroid() { |
- return mWindowAndroid; |
+ /** |
+ * @return An unused id. |
+ */ |
+ private static int generateNextId() { |
+ return sIdCounter.getAndIncrement(); |
} |
+ |
+ /** |
+ * Ensures the counter is at least as high as the specified value. The counter should always |
+ * point to an unused ID (which will be handed out next time a request comes in). Exposed so |
+ * that anything externally loading tabs and ids can set enforce new tabs start at the correct |
+ * id. |
+ * TODO(aurimas): Investigate reducing the visiblity of this method. |
+ * @param id The minimum id we should hand out to the next new tab. |
+ */ |
+ public static void incrementIdCounterTo(int id) { |
+ int diff = id - sIdCounter.get(); |
+ if (diff <= 0) return; |
+ // It's possible idCounter has been incremented between the get above and the add below |
+ // but that's OK, because in the worst case we'll overly increment idCounter. |
+ sIdCounter.addAndGet(diff); |
+ } |
+ |
+ private native void nativeInitWebContents(int nativeTabAndroid, int id, boolean incognito, |
+ ContentViewCore contentViewCore, ChromeWebContentsDelegateAndroid delegate); |
+ private native void nativeDestroyWebContents(int nativeTabAndroid, boolean deleteNative); |
+ private native Profile nativeGetProfileAndroid(int nativeTabAndroid); |
} |