mobile/android/base/Tabs.java

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
     2  * This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 package org.mozilla.gecko;
     8 import java.util.HashMap;
     9 import java.util.Iterator;
    10 import java.util.List;
    11 import java.util.concurrent.CopyOnWriteArrayList;
    12 import java.util.concurrent.atomic.AtomicInteger;
    14 import org.json.JSONObject;
    15 import org.mozilla.gecko.db.BrowserDB;
    16 import org.mozilla.gecko.favicons.Favicons;
    17 import org.mozilla.gecko.fxa.FirefoxAccounts;
    18 import org.mozilla.gecko.mozglue.JNITarget;
    19 import org.mozilla.gecko.mozglue.RobocopTarget;
    20 import org.mozilla.gecko.sync.setup.SyncAccounts;
    21 import org.mozilla.gecko.util.GeckoEventListener;
    22 import org.mozilla.gecko.util.ThreadUtils;
    24 import android.accounts.Account;
    25 import android.accounts.AccountManager;
    26 import android.accounts.OnAccountsUpdateListener;
    27 import android.content.ContentResolver;
    28 import android.content.Context;
    29 import android.database.ContentObserver;
    30 import android.graphics.Bitmap;
    31 import android.graphics.Color;
    32 import android.net.Uri;
    33 import android.os.Handler;
    34 import android.util.Log;
    36 public class Tabs implements GeckoEventListener {
    37     private static final String LOGTAG = "GeckoTabs";
    39     // mOrder and mTabs are always of the same cardinality, and contain the same values.
    40     private final CopyOnWriteArrayList<Tab> mOrder = new CopyOnWriteArrayList<Tab>();
    42     // All writes to mSelectedTab must be synchronized on the Tabs instance.
    43     // In general, it's preferred to always use selectTab()).
    44     private volatile Tab mSelectedTab;
    46     // All accesses to mTabs must be synchronized on the Tabs instance.
    47     private final HashMap<Integer, Tab> mTabs = new HashMap<Integer, Tab>();
    49     private AccountManager mAccountManager;
    50     private OnAccountsUpdateListener mAccountListener = null;
    52     public static final int LOADURL_NONE         = 0;
    53     public static final int LOADURL_NEW_TAB      = 1 << 0;
    54     public static final int LOADURL_USER_ENTERED = 1 << 1;
    55     public static final int LOADURL_PRIVATE      = 1 << 2;
    56     public static final int LOADURL_PINNED       = 1 << 3;
    57     public static final int LOADURL_DELAY_LOAD   = 1 << 4;
    58     public static final int LOADURL_DESKTOP      = 1 << 5;
    59     public static final int LOADURL_BACKGROUND   = 1 << 6;
    60     public static final int LOADURL_EXTERNAL     = 1 << 7;
    62     private static final long PERSIST_TABS_AFTER_MILLISECONDS = 1000 * 5;
    64     private static AtomicInteger sTabId = new AtomicInteger(0);
    65     private volatile boolean mInitialTabsAdded;
    67     private Context mAppContext;
    68     private ContentObserver mContentObserver;
    70     private final Runnable mPersistTabsRunnable = new Runnable() {
    71         @Override
    72         public void run() {
    73             try {
    74                 final Context context = getAppContext();
    75                 boolean syncIsSetup = SyncAccounts.syncAccountsExist(context) ||
    76                                       FirefoxAccounts.firefoxAccountsExist(context);
    77                 if (syncIsSetup) {
    78                     TabsAccessor.persistLocalTabs(getContentResolver(), getTabsInOrder());
    79                 }
    80             } catch (SecurityException se) {} // will fail without android.permission.GET_ACCOUNTS
    81         }
    82     };
    84     private Tabs() {
    85         registerEventListener("Session:RestoreEnd");
    86         registerEventListener("SessionHistory:New");
    87         registerEventListener("SessionHistory:Back");
    88         registerEventListener("SessionHistory:Forward");
    89         registerEventListener("SessionHistory:Goto");
    90         registerEventListener("SessionHistory:Purge");
    91         registerEventListener("Tab:Added");
    92         registerEventListener("Tab:Close");
    93         registerEventListener("Tab:Select");
    94         registerEventListener("Content:LocationChange");
    95         registerEventListener("Content:SecurityChange");
    96         registerEventListener("Content:ReaderEnabled");
    97         registerEventListener("Content:StateChange");
    98         registerEventListener("Content:LoadError");
    99         registerEventListener("Content:PageShow");
   100         registerEventListener("DOMContentLoaded");
   101         registerEventListener("DOMTitleChanged");
   102         registerEventListener("Link:Favicon");
   103         registerEventListener("Link:Feed");
   104         registerEventListener("Link:OpenSearch");
   105         registerEventListener("DesktopMode:Changed");
   106         registerEventListener("Tab:ViewportMetadata");
   107         registerEventListener("Tab:StreamStart");
   108         registerEventListener("Tab:StreamStop");
   110     }
   112     public synchronized void attachToContext(Context context) {
   113         final Context appContext = context.getApplicationContext();
   114         if (mAppContext == appContext) {
   115             return;
   116         }
   118         if (mAppContext != null) {
   119             // This should never happen.
   120             Log.w(LOGTAG, "The application context has changed!");
   121         }
   123         mAppContext = appContext;
   124         mAccountManager = AccountManager.get(appContext);
   126         mAccountListener = new OnAccountsUpdateListener() {
   127             @Override
   128             public void onAccountsUpdated(Account[] accounts) {
   129                 persistAllTabs();
   130             }
   131         };
   133         // The listener will run on the background thread (see 2nd argument).
   134         mAccountManager.addOnAccountsUpdatedListener(mAccountListener, ThreadUtils.getBackgroundHandler(), false);
   136         if (mContentObserver != null) {
   137             BrowserDB.registerBookmarkObserver(getContentResolver(), mContentObserver);
   138         }
   139     }
   141     /**
   142      * Gets the tab count corresponding to the private state of the selected
   143      * tab.
   144      *
   145      * If the selected tab is a non-private tab, this will return the number of
   146      * non-private tabs; likewise, if this is a private tab, this will return
   147      * the number of private tabs.
   148      *
   149      * @return the number of tabs in the current private state
   150      */
   151     public synchronized int getDisplayCount() {
   152         // Once mSelectedTab is non-null, it cannot be null for the remainder
   153         // of the object's lifetime.
   154         boolean getPrivate = mSelectedTab != null && mSelectedTab.isPrivate();
   155         int count = 0;
   156         for (Tab tab : mOrder) {
   157             if (tab.isPrivate() == getPrivate) {
   158                 count++;
   159             }
   160         }
   161         return count;
   162     }
   164     public int isOpen(String url) {
   165         for (Tab tab : mOrder) {
   166             if (tab.getURL().equals(url)) {
   167                 return tab.getId();
   168             }
   169         }
   170         return -1;
   171     }
   173     // Must be synchronized to avoid racing on mContentObserver.
   174     private void lazyRegisterBookmarkObserver() {
   175         if (mContentObserver == null) {
   176             mContentObserver = new ContentObserver(null) {
   177                 @Override
   178                 public void onChange(boolean selfChange) {
   179                     for (Tab tab : mOrder) {
   180                         tab.updateBookmark();
   181                     }
   182                 }
   183             };
   184             BrowserDB.registerBookmarkObserver(getContentResolver(), mContentObserver);
   185         }
   186     }
   188     private Tab addTab(int id, String url, boolean external, int parentId, String title, boolean isPrivate) {
   189         final Tab tab = isPrivate ? new PrivateTab(mAppContext, id, url, external, parentId, title) :
   190                                     new Tab(mAppContext, id, url, external, parentId, title);
   191         synchronized (this) {
   192             lazyRegisterBookmarkObserver();
   193             mTabs.put(id, tab);
   194             mOrder.add(tab);
   195         }
   197         // Suppress the ADDED event to prevent animation of tabs created via session restore.
   198         if (mInitialTabsAdded) {
   199             notifyListeners(tab, TabEvents.ADDED);
   200         }
   202         return tab;
   203     }
   205     public synchronized void removeTab(int id) {
   206         if (mTabs.containsKey(id)) {
   207             Tab tab = getTab(id);
   208             mOrder.remove(tab);
   209             mTabs.remove(id);
   210         }
   211     }
   213     public synchronized Tab selectTab(int id) {
   214         if (!mTabs.containsKey(id))
   215             return null;
   217         final Tab oldTab = getSelectedTab();
   218         final Tab tab = mTabs.get(id);
   220         // This avoids a NPE below, but callers need to be careful to
   221         // handle this case.
   222         if (tab == null || oldTab == tab) {
   223             return null;
   224         }
   226         mSelectedTab = tab;
   227         notifyListeners(tab, TabEvents.SELECTED);
   229         if (oldTab != null) {
   230             notifyListeners(oldTab, TabEvents.UNSELECTED);
   231         }
   233         // Pass a message to Gecko to update tab state in BrowserApp.
   234         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Tab:Selected", String.valueOf(tab.getId())));
   235         return tab;
   236     }
   238     private int getIndexOf(Tab tab) {
   239         return mOrder.lastIndexOf(tab);
   240     }
   242     private Tab getNextTabFrom(Tab tab, boolean getPrivate) {
   243         int numTabs = mOrder.size();
   244         int index = getIndexOf(tab);
   245         for (int i = index + 1; i < numTabs; i++) {
   246             Tab next = mOrder.get(i);
   247             if (next.isPrivate() == getPrivate) {
   248                 return next;
   249             }
   250         }
   251         return null;
   252     }
   254     private Tab getPreviousTabFrom(Tab tab, boolean getPrivate) {
   255         int index = getIndexOf(tab);
   256         for (int i = index - 1; i >= 0; i--) {
   257             Tab prev = mOrder.get(i);
   258             if (prev.isPrivate() == getPrivate) {
   259                 return prev;
   260             }
   261         }
   262         return null;
   263     }
   265     /**
   266      * Gets the selected tab.
   267      *
   268      * The selected tab can be null if we're doing a session restore after a
   269      * crash and Gecko isn't ready yet.
   270      *
   271      * @return the selected tab, or null if no tabs exist
   272      */
   273     public Tab getSelectedTab() {
   274         return mSelectedTab;
   275     }
   277     public boolean isSelectedTab(Tab tab) {
   278         return tab != null && tab == mSelectedTab;
   279     }
   281     public boolean isSelectedTabId(int tabId) {
   282         final Tab selected = mSelectedTab;
   283         return selected != null && selected.getId() == tabId;
   284     }
   286     @RobocopTarget
   287     public synchronized Tab getTab(int id) {
   288         if (id == -1)
   289             return null;
   291         if (mTabs.size() == 0)
   292             return null;
   294         if (!mTabs.containsKey(id))
   295            return null;
   297         return mTabs.get(id);
   298     }
   300     /** Close tab and then select the default next tab */
   301     @RobocopTarget
   302     public synchronized void closeTab(Tab tab) {
   303         closeTab(tab, getNextTab(tab));
   304     }
   306     /** Close tab and then select nextTab */
   307     public synchronized void closeTab(final Tab tab, Tab nextTab) {
   308         if (tab == null)
   309             return;
   311         int tabId = tab.getId();
   312         removeTab(tabId);
   314         if (nextTab == null) {
   315             nextTab = loadUrl(AboutPages.HOME, LOADURL_NEW_TAB);
   316         }
   318         selectTab(nextTab.getId());
   320         tab.onDestroy();
   322         // Pass a message to Gecko to update tab state in BrowserApp
   323         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Tab:Closed", String.valueOf(tabId)));
   324     }
   326     /** Return the tab that will be selected by default after this one is closed */
   327     public Tab getNextTab(Tab tab) {
   328         Tab selectedTab = getSelectedTab();
   329         if (selectedTab != tab)
   330             return selectedTab;
   332         boolean getPrivate = tab.isPrivate();
   333         Tab nextTab = getNextTabFrom(tab, getPrivate);
   334         if (nextTab == null)
   335             nextTab = getPreviousTabFrom(tab, getPrivate);
   336         if (nextTab == null && getPrivate) {
   337             // If there are no private tabs remaining, get the last normal tab
   338             Tab lastTab = mOrder.get(mOrder.size() - 1);
   339             if (!lastTab.isPrivate()) {
   340                 nextTab = lastTab;
   341             } else {
   342                 nextTab = getPreviousTabFrom(lastTab, false);
   343             }
   344         }
   346         Tab parent = getTab(tab.getParentId());
   347         if (parent != null) {
   348             // If the next tab is a sibling, switch to it. Otherwise go back to the parent.
   349             if (nextTab != null && nextTab.getParentId() == tab.getParentId())
   350                 return nextTab;
   351             else
   352                 return parent;
   353         }
   354         return nextTab;
   355     }
   357     public Iterable<Tab> getTabsInOrder() {
   358         return mOrder;
   359     }
   361     /**
   362      * @return the current GeckoApp instance, or throws if
   363      *         we aren't correctly initialized.
   364      */
   365     private synchronized Context getAppContext() {
   366         if (mAppContext == null) {
   367             throw new IllegalStateException("Tabs not initialized with a GeckoApp instance.");
   368         }
   369         return mAppContext;
   370     }
   372     public ContentResolver getContentResolver() {
   373         return getAppContext().getContentResolver();
   374     }
   376     // Make Tabs a singleton class.
   377     private static class TabsInstanceHolder {
   378         private static final Tabs INSTANCE = new Tabs();
   379     }
   381     @RobocopTarget
   382     public static Tabs getInstance() {
   383        return Tabs.TabsInstanceHolder.INSTANCE;
   384     }
   386     // GeckoEventListener implementation
   387     @Override
   388     public void handleMessage(String event, JSONObject message) {
   389         Log.d(LOGTAG, "handleMessage: " + event);
   390         try {
   391             if (event.equals("Session:RestoreEnd")) {
   392                 notifyListeners(null, TabEvents.RESTORED);
   393                 return;
   394             }
   396             // All other events handled below should contain a tabID property
   397             int id = message.getInt("tabID");
   398             Tab tab = getTab(id);
   400             // "Tab:Added" is a special case because tab will be null if the tab was just added
   401             if (event.equals("Tab:Added")) {
   402                 String url = message.isNull("uri") ? null : message.getString("uri");
   404                 if (message.getBoolean("stub")) {
   405                     if (tab == null) {
   406                         // Tab was already closed; abort
   407                         return;
   408                     }
   409                 } else {
   410                     tab = addTab(id, url, message.getBoolean("external"),
   411                                           message.getInt("parentId"),
   412                                           message.getString("title"),
   413                                           message.getBoolean("isPrivate"));
   415                     // If we added the tab as a stub, we should have already
   416                     // selected it, so ignore this flag for stubbed tabs.
   417                     if (message.getBoolean("selected"))
   418                         selectTab(id);
   419                 }
   421                 if (message.getBoolean("delayLoad"))
   422                     tab.setState(Tab.STATE_DELAYED);
   423                 if (message.getBoolean("desktopMode"))
   424                     tab.setDesktopMode(true);
   425                 return;
   426             }
   428             // Tab was already closed; abort
   429             if (tab == null)
   430                 return;
   432             if (event.startsWith("SessionHistory:")) {
   433                 event = event.substring("SessionHistory:".length());
   434                 tab.handleSessionHistoryMessage(event, message);
   435             } else if (event.equals("Tab:Close")) {
   436                 closeTab(tab);
   437             } else if (event.equals("Tab:Select")) {
   438                 selectTab(tab.getId());
   439             } else if (event.equals("Content:LocationChange")) {
   440                 tab.handleLocationChange(message);
   441             } else if (event.equals("Content:SecurityChange")) {
   442                 tab.updateIdentityData(message.getJSONObject("identity"));
   443                 notifyListeners(tab, TabEvents.SECURITY_CHANGE);
   444             } else if (event.equals("Content:ReaderEnabled")) {
   445                 tab.setReaderEnabled(true);
   446                 notifyListeners(tab, TabEvents.READER_ENABLED);
   447             } else if (event.equals("Content:StateChange")) {
   448                 int state = message.getInt("state");
   449                 if ((state & GeckoAppShell.WPL_STATE_IS_NETWORK) != 0) {
   450                     if ((state & GeckoAppShell.WPL_STATE_START) != 0) {
   451                         boolean restoring = message.getBoolean("restoring");
   452                         tab.handleDocumentStart(restoring, message.getString("uri"));
   453                         notifyListeners(tab, Tabs.TabEvents.START);
   454                     } else if ((state & GeckoAppShell.WPL_STATE_STOP) != 0) {
   455                         tab.handleDocumentStop(message.getBoolean("success"));
   456                         notifyListeners(tab, Tabs.TabEvents.STOP);
   457                     }
   458                 }
   459             } else if (event.equals("Content:LoadError")) {
   460                 tab.handleContentLoaded();
   461                 notifyListeners(tab, Tabs.TabEvents.LOAD_ERROR);
   462             } else if (event.equals("Content:PageShow")) {
   463                 notifyListeners(tab, TabEvents.PAGE_SHOW);
   464             } else if (event.equals("DOMContentLoaded")) {
   465                 tab.handleContentLoaded();
   466                 String backgroundColor = message.getString("bgColor");
   467                 if (backgroundColor != null) {
   468                     tab.setBackgroundColor(backgroundColor);
   469                 } else {
   470                     // Default to white if no color is given
   471                     tab.setBackgroundColor(Color.WHITE);
   472                 }
   473                 tab.setErrorType(message.optString("errorType"));
   474                 notifyListeners(tab, Tabs.TabEvents.LOADED);
   475             } else if (event.equals("DOMTitleChanged")) {
   476                 tab.updateTitle(message.getString("title"));
   477             } else if (event.equals("Link:Favicon")) {
   478                 tab.updateFaviconURL(message.getString("href"), message.getInt("size"));
   479                 notifyListeners(tab, TabEvents.LINK_FAVICON);
   480             } else if (event.equals("Link:Feed")) {
   481                 tab.setHasFeeds(true);
   482                 notifyListeners(tab, TabEvents.LINK_FEED);
   483             } else if (event.equals("Link:OpenSearch")) {
   484                 boolean visible = message.getBoolean("visible");
   485                 tab.setHasOpenSearch(visible);
   486             } else if (event.equals("DesktopMode:Changed")) {
   487                 tab.setDesktopMode(message.getBoolean("desktopMode"));
   488                 notifyListeners(tab, TabEvents.DESKTOP_MODE_CHANGE);
   489             } else if (event.equals("Tab:ViewportMetadata")) {
   490                 tab.setZoomConstraints(new ZoomConstraints(message));
   491                 tab.setIsRTL(message.getBoolean("isRTL"));
   492                 notifyListeners(tab, TabEvents.VIEWPORT_CHANGE);
   493             } else if (event.equals("Tab:StreamStart")) {
   494                 tab.setRecording(true);
   495                 notifyListeners(tab, TabEvents.RECORDING_CHANGE);
   496             } else if (event.equals("Tab:StreamStop")) {
   497                 tab.setRecording(false);
   498                 notifyListeners(tab, TabEvents.RECORDING_CHANGE);
   499             }
   501         } catch (Exception e) {
   502             Log.w(LOGTAG, "handleMessage threw for " + event, e);
   503         }
   504     }
   506     /**
   507      * Set the favicon for any tabs loaded with this page URL.
   508      */
   509     public void updateFaviconForURL(String pageURL, Bitmap favicon) {
   510         // The tab might be pointing to another URL by the time the
   511         // favicon is finally loaded, in which case we won't find the tab.
   512         // See also: Bug 920331.
   513         for (Tab tab : mOrder) {
   514             String tabURL = tab.getURL();
   515             if (pageURL.equals(tabURL)) {
   516                 tab.setFaviconLoadId(Favicons.NOT_LOADING);
   517                 if (tab.updateFavicon(favicon)) {
   518                     notifyListeners(tab, TabEvents.FAVICON);
   519                 }
   520             }
   521         }
   522     }
   524     public void refreshThumbnails() {
   525         final ThumbnailHelper helper = ThumbnailHelper.getInstance();
   526         ThreadUtils.postToBackgroundThread(new Runnable() {
   527             @Override
   528             public void run() {
   529                 for (final Tab tab : mOrder) {
   530                     helper.getAndProcessThumbnailFor(tab);
   531                 }
   532             }
   533         });
   534     }
   536     public interface OnTabsChangedListener {
   537         public void onTabChanged(Tab tab, TabEvents msg, Object data);
   538     }
   540     private static final List<OnTabsChangedListener> TABS_CHANGED_LISTENERS = new CopyOnWriteArrayList<OnTabsChangedListener>();
   542     public static void registerOnTabsChangedListener(OnTabsChangedListener listener) {
   543         TABS_CHANGED_LISTENERS.add(listener);
   544     }
   546     public static void unregisterOnTabsChangedListener(OnTabsChangedListener listener) {
   547         TABS_CHANGED_LISTENERS.remove(listener);
   548     }
   550     public enum TabEvents {
   551         CLOSED,
   552         START,
   553         LOADED,
   554         LOAD_ERROR,
   555         STOP,
   556         FAVICON,
   557         THUMBNAIL,
   558         TITLE,
   559         SELECTED,
   560         UNSELECTED,
   561         ADDED,
   562         RESTORED,
   563         LOCATION_CHANGE,
   564         MENU_UPDATED,
   565         PAGE_SHOW,
   566         LINK_FAVICON,
   567         LINK_FEED,
   568         SECURITY_CHANGE,
   569         READER_ENABLED,
   570         DESKTOP_MODE_CHANGE,
   571         VIEWPORT_CHANGE,
   572         RECORDING_CHANGE
   573     }
   575     public void notifyListeners(Tab tab, TabEvents msg) {
   576         notifyListeners(tab, msg, "");
   577     }
   579     public void notifyListeners(final Tab tab, final TabEvents msg, final Object data) {
   580         if (tab == null &&
   581             msg != TabEvents.RESTORED) {
   582             throw new IllegalArgumentException("onTabChanged:" + msg + " must specify a tab.");
   583         }
   585         ThreadUtils.postToUiThread(new Runnable() {
   586             @Override
   587             public void run() {
   588                 onTabChanged(tab, msg, data);
   590                 if (TABS_CHANGED_LISTENERS.isEmpty()) {
   591                     return;
   592                 }
   594                 Iterator<OnTabsChangedListener> items = TABS_CHANGED_LISTENERS.iterator();
   595                 while (items.hasNext()) {
   596                     items.next().onTabChanged(tab, msg, data);
   597                 }
   598             }
   599         });
   600     }
   602     private void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) {
   603         switch (msg) {
   604             case LOCATION_CHANGE:
   605                 queuePersistAllTabs();
   606                 break;
   607             case RESTORED:
   608                 mInitialTabsAdded = true;
   609                 break;
   611             // When one tab is deselected, another one is always selected, so only
   612             // queue a single persist operation. When tabs are added/closed, they
   613             // are also selected/unselected, so it would be redundant to also listen
   614             // for ADDED/CLOSED events.
   615             case SELECTED:
   616                 queuePersistAllTabs();
   617             case UNSELECTED:
   618                 tab.onChange();
   619                 break;
   620             default:
   621                 break;
   622         }
   623     }
   625     // This method persists the current ordered list of tabs in our tabs content provider.
   626     public void persistAllTabs() {
   627         ThreadUtils.postToBackgroundThread(mPersistTabsRunnable);
   628     }
   630     /**
   631      * Queues a request to persist tabs after PERSIST_TABS_AFTER_MILLISECONDS
   632      * milliseconds have elapsed. If any existing requests are already queued then
   633      * those requests are removed.
   634      */
   635     private void queuePersistAllTabs() {
   636         Handler backgroundHandler = ThreadUtils.getBackgroundHandler();
   637         backgroundHandler.removeCallbacks(mPersistTabsRunnable);
   638         backgroundHandler.postDelayed(mPersistTabsRunnable, PERSIST_TABS_AFTER_MILLISECONDS);
   639     }
   641     private void registerEventListener(String event) {
   642         GeckoAppShell.getEventDispatcher().registerEventListener(event, this);
   643     }
   645     /**
   646      * Looks for an open tab with the given URL.
   647      * @param url       the URL of the tab we're looking for
   648      *
   649      * @return first Tab with the given URL, or null if there is no such tab.
   650      */
   651     public Tab getFirstTabForUrl(String url) {
   652         return getFirstTabForUrlHelper(url, null);
   653     }
   655     /**
   656      * Looks for an open tab with the given URL and private state.
   657      * @param url       the URL of the tab we're looking for
   658      * @param isPrivate if true, only look for tabs that are private. if false,
   659      *                  only look for tabs that are non-private.
   660      *
   661      * @return first Tab with the given URL, or null if there is no such tab.
   662      */
   663     public Tab getFirstTabForUrl(String url, boolean isPrivate) {
   664         return getFirstTabForUrlHelper(url, isPrivate);
   665     }
   667     private Tab getFirstTabForUrlHelper(String url, Boolean isPrivate) {
   668         if (url == null) {
   669             return null;
   670         }
   672         for (Tab tab : mOrder) {
   673             if (isPrivate != null && isPrivate != tab.isPrivate()) {
   674                 continue;
   675             }
   676             String tabUrl = tab.getURL();
   677             if (AboutPages.isAboutReader(tabUrl)) {
   678                 tabUrl = ReaderModeUtils.getUrlFromAboutReader(tabUrl);
   679             }
   680             if (url.equals(tabUrl)) {
   681                 return tab;
   682             }
   683         }
   685         return null;
   686     }
   688     /**
   689      * Loads a tab with the given URL in the currently selected tab.
   690      *
   691      * @param url URL of page to load, or search term used if searchEngine is given
   692      */
   693     @RobocopTarget
   694     public Tab loadUrl(String url) {
   695         return loadUrl(url, LOADURL_NONE);
   696     }
   698     /**
   699      * Loads a tab with the given URL.
   700      *
   701      * @param url   URL of page to load, or search term used if searchEngine is given
   702      * @param flags flags used to load tab
   703      *
   704      * @return      the Tab if a new one was created; null otherwise
   705      */
   706     public Tab loadUrl(String url, int flags) {
   707         return loadUrl(url, null, -1, flags);
   708     }
   710     /**
   711      * Loads a tab with the given URL.
   712      *
   713      * @param url          URL of page to load, or search term used if searchEngine is given
   714      * @param searchEngine if given, the search engine with this name is used
   715      *                     to search for the url string; if null, the URL is loaded directly
   716      * @param parentId     ID of this tab's parent, or -1 if it has no parent
   717      * @param flags        flags used to load tab
   718      *
   719      * @return             the Tab if a new one was created; null otherwise
   720      */
   721     public Tab loadUrl(String url, String searchEngine, int parentId, int flags) {
   722         JSONObject args = new JSONObject();
   723         Tab added = null;
   724         boolean delayLoad = (flags & LOADURL_DELAY_LOAD) != 0;
   726         // delayLoad implies background tab
   727         boolean background = delayLoad || (flags & LOADURL_BACKGROUND) != 0;
   729         try {
   730             boolean isPrivate = (flags & LOADURL_PRIVATE) != 0;
   731             boolean userEntered = (flags & LOADURL_USER_ENTERED) != 0;
   732             boolean desktopMode = (flags & LOADURL_DESKTOP) != 0;
   733             boolean external = (flags & LOADURL_EXTERNAL) != 0;
   735             args.put("url", url);
   736             args.put("engine", searchEngine);
   737             args.put("parentId", parentId);
   738             args.put("userEntered", userEntered);
   739             args.put("newTab", (flags & LOADURL_NEW_TAB) != 0);
   740             args.put("isPrivate", isPrivate);
   741             args.put("pinned", (flags & LOADURL_PINNED) != 0);
   742             args.put("delayLoad", delayLoad);
   743             args.put("desktopMode", desktopMode);
   744             args.put("selected", !background);
   746             if ((flags & LOADURL_NEW_TAB) != 0) {
   747                 int tabId = getNextTabId();
   748                 args.put("tabID", tabId);
   750                 // The URL is updated for the tab once Gecko responds with the
   751                 // Tab:Added message. We can preliminarily set the tab's URL as
   752                 // long as it's a valid URI.
   753                 String tabUrl = (url != null && Uri.parse(url).getScheme() != null) ? url : null;
   755                 added = addTab(tabId, tabUrl, external, parentId, url, isPrivate);
   756                 added.setDesktopMode(desktopMode);
   757             }
   758         } catch (Exception e) {
   759             Log.w(LOGTAG, "Error building JSON arguments for loadUrl.", e);
   760         }
   762         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Tab:Load", args.toString()));
   764         if (added == null) {
   765             return null;
   766         }
   768         if (!delayLoad && !background) {
   769             selectTab(added.getId());
   770         }
   772         // TODO: surely we could just fetch *any* cached icon?
   773         if (AboutPages.isDefaultIconPage(url)) {
   774             Log.d(LOGTAG, "Setting about: tab favicon inline.");
   775             added.updateFavicon(getAboutPageFavicon(url));
   776         }
   778         return added;
   779     }
   781     /**
   782      * These favicons are only used for the URL bar, so
   783      * we fetch with that size.
   784      *
   785      * This method completes on the calling thread.
   786      */
   787     private Bitmap getAboutPageFavicon(final String url) {
   788         int faviconSize = Math.round(mAppContext.getResources().getDimension(R.dimen.browser_toolbar_favicon_size));
   789         return Favicons.getSizedFaviconForPageFromCache(url, faviconSize);
   790     }
   792     /**
   793      * Open the url as a new tab, and mark the selected tab as its "parent".
   794      *
   795      * If the url is already open in a tab, the existing tab is selected.
   796      * Use this for tabs opened by the browser chrome, so users can press the
   797      * "Back" button to return to the previous tab.
   798      *
   799      * @param url URL of page to load
   800      */
   801     public void loadUrlInTab(String url) {
   802         Iterable<Tab> tabs = getTabsInOrder();
   803         for (Tab tab : tabs) {
   804             if (url.equals(tab.getURL())) {
   805                 selectTab(tab.getId());
   806                 return;
   807             }
   808         }
   810         // getSelectedTab() can return null if no tab has been created yet
   811         // (i.e., we're restoring a session after a crash). In these cases,
   812         // don't mark any tabs as a parent.
   813         int parentId = -1;
   814         Tab selectedTab = getSelectedTab();
   815         if (selectedTab != null) {
   816             parentId = selectedTab.getId();
   817         }
   819         loadUrl(url, null, parentId, LOADURL_NEW_TAB);
   820     }
   822     /**
   823      * Gets the next tab ID.
   824      */
   825     @JNITarget
   826     public static int getNextTabId() {
   827         return sTabId.getAndIncrement();
   828     }
   829 }

mercurial