2011-11-18 10:28:17 -08:00
|
|
|
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
|
2012-05-21 04:12:37 -07:00
|
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
2011-11-18 10:28:17 -08:00
|
|
|
|
|
|
|
package org.mozilla.gecko;
|
|
|
|
|
2012-06-12 07:03:06 -07:00
|
|
|
import org.mozilla.gecko.db.BrowserDB;
|
2013-01-06 07:28:52 -08:00
|
|
|
import org.mozilla.gecko.sync.setup.SyncAccounts;
|
2013-03-15 03:52:53 -07:00
|
|
|
import org.mozilla.gecko.util.GeckoEventListener;
|
|
|
|
import org.mozilla.gecko.util.ThreadUtils;
|
2012-06-12 07:03:06 -07:00
|
|
|
|
2012-01-24 09:15:41 -08:00
|
|
|
import org.json.JSONObject;
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2013-01-06 07:28:55 -08:00
|
|
|
import android.accounts.Account;
|
|
|
|
import android.accounts.AccountManager;
|
|
|
|
import android.accounts.OnAccountsUpdateListener;
|
2011-11-18 10:28:17 -08:00
|
|
|
import android.content.ContentResolver;
|
2013-01-25 10:51:41 -08:00
|
|
|
import android.database.ContentObserver;
|
2013-02-26 11:50:04 -08:00
|
|
|
import android.graphics.Color;
|
2012-10-22 19:04:17 -07:00
|
|
|
import android.net.Uri;
|
2011-11-18 10:28:17 -08:00
|
|
|
import android.util.Log;
|
2013-04-08 00:51:56 -07:00
|
|
|
|
2012-07-27 17:53:54 -07:00
|
|
|
import java.util.ArrayList;
|
2013-03-13 21:56:50 -07:00
|
|
|
import java.util.Collections;
|
2012-07-27 17:53:54 -07:00
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.Iterator;
|
2013-03-13 21:56:50 -07:00
|
|
|
import java.util.List;
|
2012-08-09 13:52:58 -07:00
|
|
|
import java.util.concurrent.CopyOnWriteArrayList;
|
2012-10-05 14:53:44 -07:00
|
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
2012-07-27 17:53:54 -07:00
|
|
|
|
2011-11-18 10:28:17 -08:00
|
|
|
public class Tabs implements GeckoEventListener {
|
|
|
|
private static final String LOGTAG = "GeckoTabs";
|
|
|
|
|
2013-03-13 21:56:50 -07:00
|
|
|
// mOrder and mTabs are always of the same cardinality, and contain the same values.
|
2013-03-13 00:03:53 -07:00
|
|
|
private final CopyOnWriteArrayList<Tab> mOrder = new CopyOnWriteArrayList<Tab>();
|
2013-03-13 21:56:50 -07:00
|
|
|
|
|
|
|
// All writes to mSelectedTab must be synchronized on the Tabs instance.
|
|
|
|
// In general, it's preferred to always use selectTab()).
|
|
|
|
private volatile Tab mSelectedTab;
|
|
|
|
|
|
|
|
// All accesses to mTabs must be synchronized on the Tabs instance.
|
|
|
|
private final HashMap<Integer, Tab> mTabs = new HashMap<Integer, Tab>();
|
2011-11-18 10:28:17 -08:00
|
|
|
|
2012-08-09 13:52:58 -07:00
|
|
|
// Keeps track of how much has happened since we last updated our persistent tab store.
|
|
|
|
private volatile int mScore = 0;
|
|
|
|
|
2013-01-06 07:28:55 -08:00
|
|
|
private AccountManager mAccountManager;
|
|
|
|
private OnAccountsUpdateListener mAccountListener = null;
|
|
|
|
|
2012-10-05 14:51:18 -07:00
|
|
|
public static final int LOADURL_NONE = 0;
|
|
|
|
public static final int LOADURL_NEW_TAB = 1;
|
|
|
|
public static final int LOADURL_USER_ENTERED = 2;
|
2012-10-09 11:26:33 -07:00
|
|
|
public static final int LOADURL_PRIVATE = 4;
|
2012-10-15 10:40:16 -07:00
|
|
|
public static final int LOADURL_PINNED = 8;
|
2012-10-29 16:34:29 -07:00
|
|
|
public static final int LOADURL_DELAY_LOAD = 16;
|
|
|
|
public static final int LOADURL_DESKTOP = 32;
|
2013-03-26 10:23:45 -07:00
|
|
|
public static final int LOADURL_BACKGROUND = 64;
|
2012-10-05 14:51:18 -07:00
|
|
|
|
2012-08-09 13:52:58 -07:00
|
|
|
private static final int SCORE_INCREMENT_TAB_LOCATION_CHANGE = 5;
|
|
|
|
private static final int SCORE_INCREMENT_TAB_SELECTED = 10;
|
|
|
|
private static final int SCORE_THRESHOLD = 30;
|
|
|
|
|
2012-10-05 14:53:44 -07:00
|
|
|
private static AtomicInteger sTabId = new AtomicInteger(0);
|
2013-03-13 21:56:50 -07:00
|
|
|
private volatile boolean mInitialTabsAdded;
|
2012-10-05 14:53:44 -07:00
|
|
|
|
2012-07-27 23:31:54 -07:00
|
|
|
private GeckoApp mActivity;
|
2013-01-25 10:51:41 -08:00
|
|
|
private ContentObserver mContentObserver;
|
2012-07-27 23:31:54 -07:00
|
|
|
|
2011-11-18 10:28:17 -08:00
|
|
|
private Tabs() {
|
2013-02-24 20:51:05 -08:00
|
|
|
registerEventListener("Session:RestoreEnd");
|
2012-08-02 18:38:45 -07:00
|
|
|
registerEventListener("SessionHistory:New");
|
|
|
|
registerEventListener("SessionHistory:Back");
|
|
|
|
registerEventListener("SessionHistory:Forward");
|
|
|
|
registerEventListener("SessionHistory:Goto");
|
|
|
|
registerEventListener("SessionHistory:Purge");
|
|
|
|
registerEventListener("Tab:Added");
|
|
|
|
registerEventListener("Tab:Close");
|
|
|
|
registerEventListener("Tab:Select");
|
2012-08-27 13:44:51 -07:00
|
|
|
registerEventListener("Content:LocationChange");
|
2013-02-24 20:51:05 -08:00
|
|
|
registerEventListener("Content:SecurityChange");
|
|
|
|
registerEventListener("Content:ReaderEnabled");
|
|
|
|
registerEventListener("Content:StateChange");
|
|
|
|
registerEventListener("Content:LoadError");
|
|
|
|
registerEventListener("Content:PageShow");
|
2013-02-26 11:50:04 -08:00
|
|
|
registerEventListener("DOMContentLoaded");
|
2013-02-24 20:51:04 -08:00
|
|
|
registerEventListener("DOMTitleChanged");
|
2013-04-03 12:34:51 -07:00
|
|
|
registerEventListener("Link:Favicon");
|
|
|
|
registerEventListener("Link:Feed");
|
2013-02-26 11:50:04 -08:00
|
|
|
registerEventListener("DesktopMode:Changed");
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
|
2013-03-13 21:56:50 -07:00
|
|
|
public synchronized void attachToActivity(GeckoApp activity) {
|
|
|
|
if (mActivity == activity) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mActivity != null) {
|
|
|
|
detachFromActivity(mActivity);
|
|
|
|
}
|
|
|
|
|
2012-07-27 23:31:54 -07:00
|
|
|
mActivity = activity;
|
2013-01-06 07:28:55 -08:00
|
|
|
mAccountManager = AccountManager.get(mActivity);
|
|
|
|
|
2013-03-13 21:56:50 -07:00
|
|
|
mAccountListener = new OnAccountsUpdateListener() {
|
2013-02-26 21:48:00 -08:00
|
|
|
@Override
|
2013-01-06 07:28:55 -08:00
|
|
|
public void onAccountsUpdated(Account[] accounts) {
|
|
|
|
persistAllTabs();
|
|
|
|
}
|
2013-03-13 21:56:50 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
// The listener will run on the background thread (see 2nd argument).
|
2013-03-15 03:52:53 -07:00
|
|
|
mAccountManager.addOnAccountsUpdatedListener(mAccountListener, ThreadUtils.getBackgroundHandler(), false);
|
2013-03-13 21:56:50 -07:00
|
|
|
|
2013-01-25 10:51:41 -08:00
|
|
|
if (mContentObserver != null) {
|
|
|
|
BrowserDB.registerBookmarkObserver(getContentResolver(), mContentObserver);
|
|
|
|
}
|
2013-01-06 07:28:55 -08:00
|
|
|
}
|
|
|
|
|
2013-03-13 21:56:50 -07:00
|
|
|
// Ideally, this would remove the reference to the activity once it's
|
|
|
|
// detached; however, we have lifecycle issues with GeckoApp and Tabs that
|
|
|
|
// requires us to keep it around (see
|
|
|
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=844407).
|
|
|
|
public synchronized void detachFromActivity(GeckoApp activity) {
|
|
|
|
if (mContentObserver != null) {
|
|
|
|
BrowserDB.unregisterContentObserver(getContentResolver(), mContentObserver);
|
|
|
|
}
|
|
|
|
|
2013-01-06 07:28:55 -08:00
|
|
|
if (mAccountListener != null) {
|
|
|
|
mAccountManager.removeOnAccountsUpdatedListener(mAccountListener);
|
|
|
|
mAccountListener = null;
|
|
|
|
}
|
2012-07-27 23:31:54 -07:00
|
|
|
}
|
|
|
|
|
2013-03-13 21:56:50 -07:00
|
|
|
/**
|
|
|
|
* Gets the tab count corresponding to the private state of the selected
|
|
|
|
* tab.
|
|
|
|
*
|
|
|
|
* If the selected tab is a non-private tab, this will return the number of
|
|
|
|
* non-private tabs; likewise, if this is a private tab, this will return
|
|
|
|
* the number of private tabs.
|
|
|
|
*
|
|
|
|
* @return the number of tabs in the current private state
|
|
|
|
*/
|
|
|
|
public synchronized int getDisplayCount() {
|
|
|
|
// Once mSelectedTab is non-null, it cannot be null for the remainder
|
|
|
|
// of the object's lifetime.
|
2013-03-11 16:09:14 -07:00
|
|
|
boolean getPrivate = mSelectedTab != null && mSelectedTab.isPrivate();
|
|
|
|
int count = 0;
|
2013-03-13 21:56:50 -07:00
|
|
|
for (Tab tab : mOrder) {
|
2013-03-11 16:09:14 -07:00
|
|
|
if (tab.isPrivate() == getPrivate) {
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return count;
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
|
2013-03-13 21:56:50 -07:00
|
|
|
// Must be synchronized to avoid racing on mContentObserver.
|
2013-01-25 10:51:41 -08:00
|
|
|
private void lazyRegisterBookmarkObserver() {
|
|
|
|
if (mContentObserver == null) {
|
|
|
|
mContentObserver = new ContentObserver(null) {
|
2013-02-26 21:48:00 -08:00
|
|
|
@Override
|
2013-01-25 10:51:41 -08:00
|
|
|
public void onChange(boolean selfChange) {
|
2013-03-13 21:56:50 -07:00
|
|
|
for (Tab tab : mOrder) {
|
2013-01-25 10:51:41 -08:00
|
|
|
tab.updateBookmark();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
BrowserDB.registerBookmarkObserver(getContentResolver(), mContentObserver);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-10-09 11:26:33 -07:00
|
|
|
private Tab addTab(int id, String url, boolean external, int parentId, String title, boolean isPrivate) {
|
|
|
|
final Tab tab = isPrivate ? new PrivateTab(id, url, external, parentId, title) :
|
|
|
|
new Tab(id, url, external, parentId, title);
|
2013-03-13 21:56:50 -07:00
|
|
|
synchronized (this) {
|
|
|
|
lazyRegisterBookmarkObserver();
|
|
|
|
mTabs.put(id, tab);
|
|
|
|
mOrder.add(tab);
|
|
|
|
}
|
2012-01-24 09:16:18 -08:00
|
|
|
|
2013-03-13 21:56:50 -07:00
|
|
|
// Suppress the ADDED event to prevent animation of tabs created via session restore.
|
2012-11-07 18:34:54 -08:00
|
|
|
if (mInitialTabsAdded) {
|
2012-10-25 09:57:06 -07:00
|
|
|
notifyListeners(tab, TabEvents.ADDED);
|
2012-02-01 14:25:50 -08:00
|
|
|
}
|
2012-01-24 09:16:18 -08:00
|
|
|
|
2011-11-18 10:28:17 -08:00
|
|
|
return tab;
|
|
|
|
}
|
|
|
|
|
2013-03-13 21:56:50 -07:00
|
|
|
public synchronized void removeTab(int id) {
|
2012-07-13 11:58:42 -07:00
|
|
|
if (mTabs.containsKey(id)) {
|
2012-06-14 09:08:51 -07:00
|
|
|
Tab tab = getTab(id);
|
2012-07-13 11:58:42 -07:00
|
|
|
mOrder.remove(tab);
|
|
|
|
mTabs.remove(id);
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-03-13 21:56:50 -07:00
|
|
|
public synchronized Tab selectTab(int id) {
|
2012-07-13 11:58:42 -07:00
|
|
|
if (!mTabs.containsKey(id))
|
2011-11-18 10:28:17 -08:00
|
|
|
return null;
|
2012-01-24 09:15:52 -08:00
|
|
|
|
2012-01-31 06:30:47 -08:00
|
|
|
final Tab oldTab = getSelectedTab();
|
2012-07-13 11:58:42 -07:00
|
|
|
final Tab tab = mTabs.get(id);
|
2013-03-13 21:56:50 -07:00
|
|
|
|
2012-01-24 09:15:52 -08:00
|
|
|
// This avoids a NPE below, but callers need to be careful to
|
2013-03-13 21:56:50 -07:00
|
|
|
// handle this case.
|
|
|
|
if (tab == null || oldTab == tab) {
|
2012-01-24 09:15:52 -08:00
|
|
|
return null;
|
2013-03-13 21:56:50 -07:00
|
|
|
}
|
2012-01-24 09:15:52 -08:00
|
|
|
|
2012-07-13 11:58:42 -07:00
|
|
|
mSelectedTab = tab;
|
2013-03-13 21:56:50 -07:00
|
|
|
notifyListeners(tab, TabEvents.SELECTED);
|
2012-01-31 06:30:47 -08:00
|
|
|
|
2013-03-13 21:56:50 -07:00
|
|
|
if (oldTab != null) {
|
|
|
|
notifyListeners(oldTab, TabEvents.UNSELECTED);
|
|
|
|
}
|
2012-01-24 09:15:52 -08:00
|
|
|
|
2013-03-13 21:56:50 -07:00
|
|
|
// Pass a message to Gecko to update tab state in BrowserApp.
|
2012-02-08 23:18:27 -08:00
|
|
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Tab:Selected", String.valueOf(tab.getId())));
|
2012-05-10 10:41:00 -07:00
|
|
|
return tab;
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
|
2013-02-15 22:44:14 -08:00
|
|
|
private int getIndexOf(Tab tab) {
|
2012-07-13 11:58:42 -07:00
|
|
|
return mOrder.lastIndexOf(tab);
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
|
2013-02-15 22:44:14 -08:00
|
|
|
private Tab getNextTabFrom(Tab tab, boolean getPrivate) {
|
|
|
|
int numTabs = mOrder.size();
|
|
|
|
int index = getIndexOf(tab);
|
|
|
|
for (int i = index + 1; i < numTabs; i++) {
|
|
|
|
Tab next = mOrder.get(i);
|
|
|
|
if (next.isPrivate() == getPrivate) {
|
|
|
|
return next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
private Tab getPreviousTabFrom(Tab tab, boolean getPrivate) {
|
|
|
|
int numTabs = mOrder.size();
|
|
|
|
int index = getIndexOf(tab);
|
|
|
|
for (int i = index - 1; i >= 0; i--) {
|
|
|
|
Tab prev = mOrder.get(i);
|
|
|
|
if (prev.isPrivate() == getPrivate) {
|
|
|
|
return prev;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
|
2013-01-14 11:21:26 -08:00
|
|
|
/**
|
|
|
|
* Gets the selected tab.
|
|
|
|
*
|
|
|
|
* The selected tab can be null if we're doing a session restore after a
|
|
|
|
* crash and Gecko isn't ready yet.
|
|
|
|
*
|
|
|
|
* @return the selected tab, or null if no tabs exist
|
|
|
|
*/
|
2011-11-18 10:28:17 -08:00
|
|
|
public Tab getSelectedTab() {
|
2012-07-13 11:58:42 -07:00
|
|
|
return mSelectedTab;
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isSelectedTab(Tab tab) {
|
2013-03-13 21:56:50 -07:00
|
|
|
return tab != null && tab == mSelectedTab;
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
|
2013-03-13 21:56:50 -07:00
|
|
|
public synchronized Tab getTab(int id) {
|
2013-03-11 16:09:14 -07:00
|
|
|
if (mTabs.size() == 0)
|
2011-11-18 10:28:17 -08:00
|
|
|
return null;
|
|
|
|
|
2012-07-13 11:58:42 -07:00
|
|
|
if (!mTabs.containsKey(id))
|
2011-11-18 10:28:17 -08:00
|
|
|
return null;
|
|
|
|
|
2012-07-13 11:58:42 -07:00
|
|
|
return mTabs.get(id);
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
|
2011-12-19 10:44:52 -08:00
|
|
|
/** Close tab and then select the default next tab */
|
2013-03-13 21:56:50 -07:00
|
|
|
public synchronized void closeTab(Tab tab) {
|
2011-12-19 10:44:52 -08:00
|
|
|
closeTab(tab, getNextTab(tab));
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Close tab and then select nextTab */
|
2013-03-13 21:56:50 -07:00
|
|
|
public synchronized void closeTab(final Tab tab, Tab nextTab) {
|
2012-11-06 11:10:10 -08:00
|
|
|
if (tab == null)
|
2011-12-19 10:44:52 -08:00
|
|
|
return;
|
|
|
|
|
2012-11-06 11:10:10 -08:00
|
|
|
if (nextTab == null)
|
|
|
|
nextTab = loadUrl("about:home", LOADURL_NEW_TAB);
|
|
|
|
|
2012-06-14 20:12:06 -07:00
|
|
|
selectTab(nextTab.getId());
|
|
|
|
|
2012-01-24 09:15:41 -08:00
|
|
|
int tabId = tab.getId();
|
|
|
|
removeTab(tabId);
|
|
|
|
|
2012-10-25 09:57:06 -07:00
|
|
|
tab.onDestroy();
|
2012-01-24 09:15:41 -08:00
|
|
|
|
|
|
|
// Pass a message to Gecko to update tab state in BrowserApp
|
2012-02-08 23:18:27 -08:00
|
|
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Tab:Closed", String.valueOf(tabId)));
|
2011-12-19 10:44:52 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Return the tab that will be selected by default after this one is closed */
|
|
|
|
public Tab getNextTab(Tab tab) {
|
|
|
|
Tab selectedTab = getSelectedTab();
|
|
|
|
if (selectedTab != tab)
|
|
|
|
return selectedTab;
|
|
|
|
|
2013-02-15 22:44:14 -08:00
|
|
|
boolean getPrivate = tab.isPrivate();
|
|
|
|
Tab nextTab = getNextTabFrom(tab, getPrivate);
|
2011-12-19 10:44:52 -08:00
|
|
|
if (nextTab == null)
|
2013-02-15 22:44:14 -08:00
|
|
|
nextTab = getPreviousTabFrom(tab, getPrivate);
|
|
|
|
if (nextTab == null && getPrivate) {
|
|
|
|
// If there are no private tabs remaining, get the last normal tab
|
|
|
|
Tab lastTab = mOrder.get(mOrder.size() - 1);
|
|
|
|
nextTab = getPreviousTabFrom(lastTab, false);
|
|
|
|
}
|
2011-12-19 10:44:52 -08:00
|
|
|
|
|
|
|
Tab parent = getTab(tab.getParentId());
|
|
|
|
if (parent != null) {
|
|
|
|
// If the next tab is a sibling, switch to it. Otherwise go back to the parent.
|
|
|
|
if (nextTab != null && nextTab.getParentId() == tab.getParentId())
|
|
|
|
return nextTab;
|
|
|
|
else
|
|
|
|
return parent;
|
|
|
|
}
|
|
|
|
return nextTab;
|
|
|
|
}
|
|
|
|
|
2012-08-09 13:52:58 -07:00
|
|
|
public Iterable<Tab> getTabsInOrder() {
|
2012-07-13 11:58:42 -07:00
|
|
|
return mOrder;
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
|
2013-03-13 21:56:50 -07:00
|
|
|
/**
|
|
|
|
* @return the current GeckoApp instance, or throws if
|
|
|
|
* we aren't correctly initialized.
|
|
|
|
*/
|
|
|
|
private synchronized GeckoApp getActivity() {
|
|
|
|
if (mActivity == null) {
|
|
|
|
throw new IllegalStateException("Tabs not initialized with a GeckoApp instance.");
|
|
|
|
}
|
|
|
|
return mActivity;
|
|
|
|
}
|
|
|
|
|
2011-11-18 10:28:17 -08:00
|
|
|
public ContentResolver getContentResolver() {
|
2013-03-13 21:56:50 -07:00
|
|
|
return getActivity().getContentResolver();
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
|
2013-03-13 21:56:50 -07:00
|
|
|
// Make Tabs a singleton class.
|
2011-11-18 10:28:17 -08:00
|
|
|
private static class TabsInstanceHolder {
|
|
|
|
private static final Tabs INSTANCE = new Tabs();
|
|
|
|
}
|
|
|
|
|
|
|
|
public static Tabs getInstance() {
|
|
|
|
return Tabs.TabsInstanceHolder.INSTANCE;
|
|
|
|
}
|
|
|
|
|
|
|
|
// GeckoEventListener implementation
|
|
|
|
|
2013-02-26 21:48:00 -08:00
|
|
|
@Override
|
2011-11-18 10:28:17 -08:00
|
|
|
public void handleMessage(String event, JSONObject message) {
|
|
|
|
try {
|
2013-02-24 20:51:05 -08:00
|
|
|
if (event.equals("Session:RestoreEnd")) {
|
|
|
|
notifyListeners(null, TabEvents.RESTORED);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// All other events handled below should contain a tabID property
|
|
|
|
int id = message.getInt("tabID");
|
|
|
|
Tab tab = getTab(id);
|
|
|
|
|
|
|
|
// "Tab:Added" is a special case because tab will be null if the tab was just added
|
|
|
|
if (event.equals("Tab:Added")) {
|
2012-10-29 16:34:29 -07:00
|
|
|
String url = message.isNull("uri") ? null : message.getString("uri");
|
2012-10-05 14:53:44 -07:00
|
|
|
|
2013-02-12 23:48:32 -08:00
|
|
|
if (message.getBoolean("stub")) {
|
2013-02-24 20:51:05 -08:00
|
|
|
if (tab == null) {
|
2013-02-12 23:48:32 -08:00
|
|
|
// Tab was already closed; abort
|
|
|
|
return;
|
|
|
|
}
|
2013-02-24 20:51:05 -08:00
|
|
|
tab.updateURL(url);
|
2012-10-05 14:53:44 -07:00
|
|
|
} else {
|
2012-10-29 16:34:29 -07:00
|
|
|
tab = addTab(id, url, message.getBoolean("external"),
|
|
|
|
message.getInt("parentId"),
|
|
|
|
message.getString("title"),
|
|
|
|
message.getBoolean("isPrivate"));
|
2012-10-05 14:53:44 -07:00
|
|
|
}
|
|
|
|
|
2012-01-24 09:16:26 -08:00
|
|
|
if (message.getBoolean("selected"))
|
2013-02-24 20:51:05 -08:00
|
|
|
selectTab(id);
|
2012-01-31 14:13:33 -08:00
|
|
|
if (message.getBoolean("delayLoad"))
|
2012-03-07 13:58:31 -08:00
|
|
|
tab.setState(Tab.STATE_DELAYED);
|
2012-07-02 12:42:11 -07:00
|
|
|
if (message.getBoolean("desktopMode"))
|
|
|
|
tab.setDesktopMode(true);
|
2013-02-24 20:51:05 -08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Tab was already closed; abort
|
|
|
|
if (tab == null)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (event.startsWith("SessionHistory:")) {
|
|
|
|
event = event.substring("SessionHistory:".length());
|
|
|
|
tab.handleSessionHistoryMessage(event, message);
|
2012-01-24 09:16:26 -08:00
|
|
|
} else if (event.equals("Tab:Close")) {
|
|
|
|
closeTab(tab);
|
|
|
|
} else if (event.equals("Tab:Select")) {
|
2013-02-24 20:51:05 -08:00
|
|
|
selectTab(tab.getId());
|
2012-08-27 13:44:51 -07:00
|
|
|
} else if (event.equals("Content:LocationChange")) {
|
2013-02-24 20:51:05 -08:00
|
|
|
tab.handleLocationChange(message);
|
2013-02-24 20:51:05 -08:00
|
|
|
} else if (event.equals("Content:SecurityChange")) {
|
|
|
|
tab.updateIdentityData(message.getJSONObject("identity"));
|
|
|
|
notifyListeners(tab, TabEvents.SECURITY_CHANGE);
|
|
|
|
} else if (event.equals("Content:ReaderEnabled")) {
|
|
|
|
tab.setReaderEnabled(true);
|
|
|
|
notifyListeners(tab, TabEvents.READER_ENABLED);
|
|
|
|
} else if (event.equals("Content:StateChange")) {
|
|
|
|
int state = message.getInt("state");
|
|
|
|
if ((state & GeckoAppShell.WPL_STATE_IS_NETWORK) != 0) {
|
|
|
|
if ((state & GeckoAppShell.WPL_STATE_START) != 0) {
|
|
|
|
boolean showProgress = message.getBoolean("showProgress");
|
|
|
|
tab.handleDocumentStart(showProgress, message.getString("uri"));
|
|
|
|
notifyListeners(tab, Tabs.TabEvents.START, showProgress);
|
|
|
|
} else if ((state & GeckoAppShell.WPL_STATE_STOP) != 0) {
|
|
|
|
tab.handleDocumentStop(message.getBoolean("success"));
|
|
|
|
notifyListeners(tab, Tabs.TabEvents.STOP);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (event.equals("Content:LoadError")) {
|
|
|
|
notifyListeners(tab, Tabs.TabEvents.LOAD_ERROR);
|
|
|
|
} else if (event.equals("Content:PageShow")) {
|
|
|
|
notifyListeners(tab, TabEvents.PAGE_SHOW);
|
2013-02-26 11:50:04 -08:00
|
|
|
} else if (event.equals("DOMContentLoaded")) {
|
|
|
|
String backgroundColor = message.getString("bgColor");
|
|
|
|
if (backgroundColor != null) {
|
|
|
|
tab.setBackgroundColor(backgroundColor);
|
|
|
|
} else {
|
|
|
|
// Default to white if no color is given
|
|
|
|
tab.setBackgroundColor(Color.WHITE);
|
|
|
|
}
|
|
|
|
notifyListeners(tab, Tabs.TabEvents.LOADED);
|
2013-02-24 20:51:04 -08:00
|
|
|
} else if (event.equals("DOMTitleChanged")) {
|
2013-02-24 20:51:05 -08:00
|
|
|
tab.updateTitle(message.getString("title"));
|
2013-04-03 12:34:51 -07:00
|
|
|
} else if (event.equals("Link:Favicon")) {
|
2013-02-24 20:51:05 -08:00
|
|
|
tab.updateFaviconURL(message.getString("href"), message.getInt("size"));
|
2013-04-03 12:34:51 -07:00
|
|
|
notifyListeners(tab, TabEvents.LINK_FAVICON);
|
|
|
|
} else if (event.equals("Link:Feed")) {
|
|
|
|
tab.setFeedsEnabled(true);
|
|
|
|
notifyListeners(tab, TabEvents.LINK_FEED);
|
2013-02-26 11:50:04 -08:00
|
|
|
} else if (event.equals("DesktopMode:Changed")) {
|
|
|
|
tab.setDesktopMode(message.getBoolean("desktopMode"));
|
|
|
|
notifyListeners(tab, TabEvents.DESKTOP_MODE_CHANGE);
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
2013-03-13 21:56:50 -07:00
|
|
|
} catch (Exception e) {
|
2012-10-26 15:47:35 -07:00
|
|
|
Log.w(LOGTAG, "handleMessage threw for " + event, e);
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|
|
|
|
}
|
2012-01-16 19:23:04 -08:00
|
|
|
|
|
|
|
public void refreshThumbnails() {
|
2012-12-03 11:27:34 -08:00
|
|
|
final ThumbnailHelper helper = ThumbnailHelper.getInstance();
|
2013-03-15 03:52:53 -07:00
|
|
|
ThreadUtils.postToBackgroundThread(new Runnable() {
|
2013-03-13 21:56:50 -07:00
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
for (final Tab tab : mOrder) {
|
2012-12-03 11:27:34 -08:00
|
|
|
helper.getAndProcessThumbnailFor(tab);
|
2012-01-26 14:57:15 -08:00
|
|
|
}
|
2013-03-13 21:56:50 -07:00
|
|
|
}
|
|
|
|
});
|
2012-01-16 19:23:04 -08:00
|
|
|
}
|
2012-02-29 09:09:43 -08:00
|
|
|
|
|
|
|
public interface OnTabsChangedListener {
|
2012-06-11 15:18:40 -07:00
|
|
|
public void onTabChanged(Tab tab, TabEvents msg, Object data);
|
2012-02-29 09:09:43 -08:00
|
|
|
}
|
2013-03-13 21:56:50 -07:00
|
|
|
|
|
|
|
private static List<OnTabsChangedListener> mTabsChangedListeners =
|
|
|
|
Collections.synchronizedList(new ArrayList<OnTabsChangedListener>());
|
2012-02-29 09:09:43 -08:00
|
|
|
|
|
|
|
public static void registerOnTabsChangedListener(OnTabsChangedListener listener) {
|
|
|
|
mTabsChangedListeners.add(listener);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void unregisterOnTabsChangedListener(OnTabsChangedListener listener) {
|
|
|
|
mTabsChangedListeners.remove(listener);
|
|
|
|
}
|
|
|
|
|
|
|
|
public enum TabEvents {
|
|
|
|
CLOSED,
|
|
|
|
START,
|
|
|
|
LOADED,
|
2012-06-11 15:18:40 -07:00
|
|
|
LOAD_ERROR,
|
2012-02-29 09:09:43 -08:00
|
|
|
STOP,
|
|
|
|
FAVICON,
|
|
|
|
THUMBNAIL,
|
|
|
|
TITLE,
|
2012-06-11 15:18:40 -07:00
|
|
|
SELECTED,
|
|
|
|
UNSELECTED,
|
|
|
|
ADDED,
|
|
|
|
RESTORED,
|
2012-07-24 16:12:12 -07:00
|
|
|
LOCATION_CHANGE,
|
2013-02-24 20:51:04 -08:00
|
|
|
MENU_UPDATED,
|
2013-02-24 20:51:05 -08:00
|
|
|
PAGE_SHOW,
|
2013-04-03 12:34:51 -07:00
|
|
|
LINK_FAVICON,
|
|
|
|
LINK_FEED,
|
2013-02-24 20:51:05 -08:00
|
|
|
SECURITY_CHANGE,
|
2013-02-26 11:50:04 -08:00
|
|
|
READER_ENABLED,
|
|
|
|
DESKTOP_MODE_CHANGE
|
2012-02-29 09:09:43 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
public void notifyListeners(Tab tab, TabEvents msg) {
|
2012-06-11 15:18:40 -07:00
|
|
|
notifyListeners(tab, msg, "");
|
|
|
|
}
|
|
|
|
|
2013-03-13 21:56:50 -07:00
|
|
|
// Throws if not initialized.
|
2012-10-25 09:57:06 -07:00
|
|
|
public void notifyListeners(final Tab tab, final TabEvents msg, final Object data) {
|
2013-03-13 21:56:50 -07:00
|
|
|
getActivity().runOnUiThread(new Runnable() {
|
2013-02-26 21:48:00 -08:00
|
|
|
@Override
|
2012-10-25 09:57:06 -07:00
|
|
|
public void run() {
|
|
|
|
onTabChanged(tab, msg, data);
|
2012-08-09 13:52:58 -07:00
|
|
|
|
2013-03-13 21:56:50 -07:00
|
|
|
synchronized (mTabsChangedListeners) {
|
|
|
|
if (mTabsChangedListeners.isEmpty()) {
|
|
|
|
return;
|
|
|
|
}
|
2012-02-29 09:09:43 -08:00
|
|
|
|
2013-03-13 21:56:50 -07:00
|
|
|
Iterator<OnTabsChangedListener> items = mTabsChangedListeners.iterator();
|
|
|
|
while (items.hasNext()) {
|
|
|
|
items.next().onTabChanged(tab, msg, data);
|
|
|
|
}
|
2012-10-25 09:57:06 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2012-02-29 09:09:43 -08:00
|
|
|
}
|
2012-06-11 15:18:40 -07:00
|
|
|
|
2012-08-09 13:52:58 -07:00
|
|
|
private void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) {
|
2013-03-13 21:56:50 -07:00
|
|
|
switch (msg) {
|
2012-08-09 13:52:58 -07:00
|
|
|
case LOCATION_CHANGE:
|
|
|
|
mScore += SCORE_INCREMENT_TAB_LOCATION_CHANGE;
|
|
|
|
break;
|
2012-11-07 18:34:54 -08:00
|
|
|
case RESTORED:
|
|
|
|
mInitialTabsAdded = true;
|
|
|
|
break;
|
2012-08-09 13:52:58 -07:00
|
|
|
|
|
|
|
// When one tab is deselected, another one is always selected, so only
|
|
|
|
// increment the score once. When tabs are added/closed, they are also
|
|
|
|
// selected/unselected, so it would be redundant to also listen
|
|
|
|
// for ADDED/CLOSED events.
|
|
|
|
case SELECTED:
|
|
|
|
mScore += SCORE_INCREMENT_TAB_SELECTED;
|
|
|
|
case UNSELECTED:
|
|
|
|
tab.onChange();
|
|
|
|
break;
|
2013-03-13 21:56:50 -07:00
|
|
|
default:
|
|
|
|
break;
|
2012-08-09 13:52:58 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (mScore > SCORE_THRESHOLD) {
|
|
|
|
persistAllTabs();
|
|
|
|
mScore = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// This method persists the current ordered list of tabs in our tabs content provider.
|
|
|
|
public void persistAllTabs() {
|
2013-03-13 21:56:50 -07:00
|
|
|
final GeckoApp activity = getActivity();
|
2012-08-09 13:52:58 -07:00
|
|
|
final Iterable<Tab> tabs = getTabsInOrder();
|
2013-03-15 03:52:53 -07:00
|
|
|
ThreadUtils.postToBackgroundThread(new Runnable() {
|
2013-02-26 21:48:00 -08:00
|
|
|
@Override
|
2012-08-09 13:52:58 -07:00
|
|
|
public void run() {
|
2013-03-13 21:56:50 -07:00
|
|
|
boolean syncIsSetup = SyncAccounts.syncAccountsExist(activity);
|
|
|
|
if (syncIsSetup) {
|
2013-01-06 07:28:52 -08:00
|
|
|
TabsAccessor.persistLocalTabs(getContentResolver(), tabs);
|
2013-03-13 21:56:50 -07:00
|
|
|
}
|
2012-08-09 13:52:58 -07:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2012-08-02 18:38:45 -07:00
|
|
|
|
|
|
|
private void registerEventListener(String event) {
|
|
|
|
GeckoAppShell.getEventDispatcher().registerEventListener(event, this);
|
|
|
|
}
|
2012-10-05 14:51:18 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Loads a tab with the given URL in the currently selected tab.
|
|
|
|
*
|
|
|
|
* @param url URL of page to load, or search term used if searchEngine is given
|
|
|
|
*/
|
|
|
|
public void loadUrl(String url) {
|
|
|
|
loadUrl(url, LOADURL_NONE);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Loads a tab with the given URL.
|
|
|
|
*
|
|
|
|
* @param url URL of page to load, or search term used if searchEngine is given
|
|
|
|
* @param flags flags used to load tab
|
2012-10-15 10:40:16 -07:00
|
|
|
*
|
|
|
|
* @return the Tab if a new one was created; null otherwise
|
2012-10-05 14:51:18 -07:00
|
|
|
*/
|
2012-10-15 10:40:16 -07:00
|
|
|
public Tab loadUrl(String url, int flags) {
|
|
|
|
return loadUrl(url, null, -1, flags);
|
2012-10-05 14:51:18 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Loads a tab with the given URL.
|
|
|
|
*
|
|
|
|
* @param url URL of page to load, or search term used if searchEngine is given
|
|
|
|
* @param searchEngine if given, the search engine with this name is used
|
|
|
|
* to search for the url string; if null, the URL is loaded directly
|
|
|
|
* @param parentId ID of this tab's parent, or -1 if it has no parent
|
|
|
|
* @param flags flags used to load tab
|
2012-10-15 10:40:16 -07:00
|
|
|
*
|
|
|
|
* @return the Tab if a new one was created; null otherwise
|
2012-10-05 14:51:18 -07:00
|
|
|
*/
|
2012-10-15 10:40:16 -07:00
|
|
|
public Tab loadUrl(String url, String searchEngine, int parentId, int flags) {
|
2012-10-05 14:51:18 -07:00
|
|
|
JSONObject args = new JSONObject();
|
2012-10-15 10:40:16 -07:00
|
|
|
Tab added = null;
|
2012-10-29 16:34:29 -07:00
|
|
|
boolean delayLoad = (flags & LOADURL_DELAY_LOAD) != 0;
|
2013-03-26 10:23:45 -07:00
|
|
|
boolean background = (flags & LOADURL_BACKGROUND) != 0;
|
2012-10-05 14:53:44 -07:00
|
|
|
|
2012-10-05 14:51:18 -07:00
|
|
|
try {
|
2012-10-09 11:26:33 -07:00
|
|
|
boolean isPrivate = (flags & LOADURL_PRIVATE) != 0;
|
2012-10-15 10:40:16 -07:00
|
|
|
boolean userEntered = (flags & LOADURL_USER_ENTERED) != 0;
|
2012-10-29 16:34:29 -07:00
|
|
|
boolean desktopMode = (flags & LOADURL_DESKTOP) != 0;
|
2012-10-09 11:26:33 -07:00
|
|
|
|
2012-10-05 14:51:18 -07:00
|
|
|
args.put("url", url);
|
|
|
|
args.put("engine", searchEngine);
|
|
|
|
args.put("parentId", parentId);
|
2012-10-15 10:40:16 -07:00
|
|
|
args.put("userEntered", userEntered);
|
2012-10-05 14:51:18 -07:00
|
|
|
args.put("newTab", (flags & LOADURL_NEW_TAB) != 0);
|
2012-10-09 11:26:33 -07:00
|
|
|
args.put("isPrivate", isPrivate);
|
2012-10-15 10:40:16 -07:00
|
|
|
args.put("pinned", (flags & LOADURL_PINNED) != 0);
|
2012-10-29 16:34:29 -07:00
|
|
|
args.put("delayLoad", delayLoad);
|
|
|
|
args.put("desktopMode", desktopMode);
|
2013-03-26 10:23:45 -07:00
|
|
|
args.put("selected", !background);
|
2012-10-05 14:53:44 -07:00
|
|
|
|
|
|
|
if ((flags & LOADURL_NEW_TAB) != 0) {
|
2012-10-29 16:34:29 -07:00
|
|
|
int tabId = getNextTabId();
|
2012-10-05 14:53:44 -07:00
|
|
|
args.put("tabID", tabId);
|
2012-10-22 19:04:17 -07:00
|
|
|
|
|
|
|
// The URL is updated for the tab once Gecko responds with the
|
|
|
|
// Tab:Added message. We can preliminarily set the tab's URL as
|
|
|
|
// long as it's a valid URI.
|
|
|
|
String tabUrl = (url != null && Uri.parse(url).getScheme() != null) ? url : null;
|
|
|
|
|
|
|
|
added = addTab(tabId, tabUrl, false, parentId, url, isPrivate);
|
2012-10-29 16:34:29 -07:00
|
|
|
added.setDesktopMode(desktopMode);
|
2012-10-05 14:53:44 -07:00
|
|
|
}
|
2012-10-05 14:51:18 -07:00
|
|
|
} catch (Exception e) {
|
2012-10-29 16:34:29 -07:00
|
|
|
Log.w(LOGTAG, "Error building JSON arguments for loadUrl.", e);
|
2012-10-05 14:51:18 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Tab:Load", args.toString()));
|
2012-10-05 14:53:44 -07:00
|
|
|
|
2013-03-26 10:23:45 -07:00
|
|
|
if ((added != null) && !delayLoad && !background) {
|
2012-10-29 16:34:29 -07:00
|
|
|
selectTab(added.getId());
|
2012-10-05 14:53:44 -07:00
|
|
|
}
|
2012-10-15 10:40:16 -07:00
|
|
|
|
|
|
|
return added;
|
2012-10-05 14:51:18 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Open the url as a new tab, and mark the selected tab as its "parent".
|
|
|
|
*
|
|
|
|
* If the url is already open in a tab, the existing tab is selected.
|
|
|
|
* Use this for tabs opened by the browser chrome, so users can press the
|
|
|
|
* "Back" button to return to the previous tab.
|
|
|
|
*
|
|
|
|
* @param url URL of page to load
|
|
|
|
*/
|
|
|
|
public void loadUrlInTab(String url) {
|
|
|
|
Iterable<Tab> tabs = getTabsInOrder();
|
|
|
|
for (Tab tab : tabs) {
|
|
|
|
if (url.equals(tab.getURL())) {
|
|
|
|
selectTab(tab.getId());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-10-22 12:42:14 -07:00
|
|
|
// getSelectedTab() can return null if no tab has been created yet
|
|
|
|
// (i.e., we're restoring a session after a crash). In these cases,
|
|
|
|
// don't mark any tabs as a parent.
|
|
|
|
int parentId = -1;
|
|
|
|
Tab selectedTab = getSelectedTab();
|
|
|
|
if (selectedTab != null) {
|
|
|
|
parentId = selectedTab.getId();
|
|
|
|
}
|
|
|
|
|
|
|
|
loadUrl(url, null, parentId, LOADURL_NEW_TAB);
|
2012-10-05 14:51:18 -07:00
|
|
|
}
|
2012-10-05 14:53:44 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the next tab ID.
|
|
|
|
*
|
|
|
|
* This method is invoked via JNI.
|
|
|
|
*/
|
|
|
|
public static int getNextTabId() {
|
|
|
|
return sTabId.getAndIncrement();
|
|
|
|
}
|
2011-11-18 10:28:17 -08:00
|
|
|
}
|