#changeset: 78790:3f21e8a211ff
authorDoug Turner <dougt@dougt.org>
Fri, 14 Oct 2011 22:35:15 -0700
changeset 83193 2cd2d2294a70028a596a4fa0de0a388ff2863cc7
parent 83192 2eb6f5137a14b58fb1d1b10eef944b8ecf622486
child 83194 2160392609ed28604d167919df0e26ad1250aade
push id519
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 00:38:35 +0000
treeherdermozilla-beta@788ea1ef610b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs78790
milestone10.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
#changeset: 78790:3f21e8a211ff #tag: qbase #tag: qtip #tag: tabs_in_nativeui #tag: tip #user: Sriram Ramasubramanian <sriram@mozilla.com> #date: Fri Oct 14 18:58:02 2011 -0700 #summary: Support for tabs in native ui.
embedding/android/AndroidManifest.xml.in
embedding/android/AwesomeBar.java
embedding/android/GeckoApp.java
embedding/android/GeckoAppShell.java
embedding/android/Makefile.in
embedding/android/SessionHistory.java
embedding/android/ShowTabs.java
embedding/android/Tab.java
embedding/android/Tabs.java
embedding/android/resources/layout/awesomebar_row.xml
embedding/android/resources/layout/gecko_menu.xml
embedding/android/resources/layout/show_tabs.xml
mobile/chrome/content/browser.js
--- a/embedding/android/AndroidManifest.xml.in
+++ b/embedding/android/AndroidManifest.xml.in
@@ -137,10 +137,20 @@
         </activity>
 	<activity android:name="org.mozilla.gecko.GeckoBookmarks"
 		  android:label="@string/bookmarks_title"
 		  android:excludeFromRecents="true">
 	  <intent-filter>
 	    <action android:name="android.intent.action.MAIN" />
 	  </intent-filter>
         </activity>
+
+        <activity android:name="org.mozilla.gecko.ShowTabs"
+                  android:theme="@android:style/Theme.Translucent"
+                  android:windowSoftInputMode="stateUnspecified|adjustResize"
+                  android:windowIsTranslucent="true"
+                  android:windowContentOverlay="@null"
+                  android:windowNoTitle="true"
+                  android:windowIsFloating="true"
+                  android:backgroundDimEnabled="false">
+        </activity>
     </application>
 </manifest> 
--- a/embedding/android/AwesomeBar.java
+++ b/embedding/android/AwesomeBar.java
@@ -55,16 +55,20 @@ import android.view.*;
 import android.view.View.*;
 import android.net.Uri;
 import android.graphics.*;
 
 public class AwesomeBar extends ListActivity {
     public static final String URL_KEY = "url";
     public static final String TITLE_KEY = "title";
     public static final String CURRENT_URL_KEY = "currenturl";
+    public static final String TYPE = "type";
+    public static enum Type { ADD, EDIT };
+    
+    private String mType;
 
     public class AwesomeBarCursorAdapter extends SimpleCursorAdapter {
         private Cursor mAdapterCursor;
         private Context mContext;
 
         public AwesomeBarCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to) {
             // Using the older, deprecated constructor so we can work on API < 11
             super(context, layout, c, from, to);
@@ -127,17 +131,19 @@ public class AwesomeBar extends ListActi
             null,
             new String[] { TITLE_KEY, URL_KEY },
             new int[] { R.id.title, R.id.url }
         );
         setListAdapter(adapter);
 
         final EditText text = (EditText)findViewById(R.id.awesomebar_text);
 
-        String currentUrl = getIntent().getStringExtra(CURRENT_URL_KEY);
+        Intent intent = getIntent();
+        String currentUrl = intent.getStringExtra(CURRENT_URL_KEY);
+        mType = intent.getStringExtra(TYPE);
         if (currentUrl != null) {
             text.setText(currentUrl);
             text.selectAll();
         }
 
         text.addTextChangedListener(new TextWatcher() {
                 
             public void afterTextChanged(Editable s) {
@@ -154,18 +160,22 @@ public class AwesomeBar extends ListActi
                 adapter.getFilter().filter(s.toString());
             }
                 
         });
 
         text.setOnKeyListener(new View.OnKeyListener() {
             public boolean onKey(View v, int keyCode, KeyEvent event) {
                 if (keyCode == KeyEvent.KEYCODE_ENTER) {
+                    if (event.getAction() != KeyEvent.ACTION_DOWN)
+                        return true;
+
                     Intent resultIntent = new Intent();
                     resultIntent.putExtra(URL_KEY, text.getText().toString());
+                    resultIntent.putExtra(TYPE, mType);
                     setResult(Activity.RESULT_OK, resultIntent);
                     finish();
                     return true;
                 } else {
                     return false;
                 }
             }
         });
@@ -201,12 +211,13 @@ public class AwesomeBar extends ListActi
     }
 
     @Override
     public void onListItemClick(ListView l, View v, int position, long id) {
         Cursor cursor = (Cursor)l.getItemAtPosition(position);
         String url = cursor.getString(cursor.getColumnIndexOrThrow(URL_KEY));
         Intent resultIntent = new Intent();
         resultIntent.putExtra(URL_KEY, url);
+        resultIntent.putExtra(TYPE, mType);
         setResult(Activity.RESULT_OK, resultIntent);
         finish();
     }
 }
--- a/embedding/android/GeckoApp.java
+++ b/embedding/android/GeckoApp.java
@@ -84,31 +84,32 @@ abstract public class GeckoApp
     private LinearLayout mMainLayout;
     private RelativeLayout mGeckoLayout;
     public static GeckoSurfaceView surfaceView;
     public static SurfaceView cameraView;
     public static GeckoApp mAppContext;
     public static boolean mFullscreen = false;
     public static File sGREDir = null;
     public Handler mMainHandler;
+    public static DatabaseHelper mDbHelper;
     private IntentFilter mConnectivityFilter;
     private BroadcastReceiver mConnectivityReceiver;
     private Button mAwesomeBar;
     private ImageButton mFavicon;
     private ProgressBar mProgressBar;
-    private static SessionHistory mSessionHistory;
 
     enum LaunchState {Launching, WaitButton,
                       Launched, GeckoRunning, GeckoExiting};
     private static LaunchState sLaunchState = LaunchState.Launching;
     private static boolean sTryCatchAttached = false;
 
     private static final int FILE_PICKER_REQUEST = 1;
     private static final int AWESOMEBAR_REQUEST = 2;
     private static final int CAMERA_CAPTURE_REQUEST = 3;
+    private static final int SHOW_TABS_REQUEST = 4;
 
     static boolean checkLaunchState(LaunchState checkState) {
         synchronized(sLaunchState) {
             return sLaunchState == checkState;
         }
     }
 
     static void setLaunchState(LaunchState setState) {
@@ -371,91 +372,134 @@ abstract public class GeckoApp
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
         switch (item.getItemId()) {
            case R.id.quit:
                quit();
                return true;
            case R.id.bookmarks:
                Intent intent = new Intent(this, GeckoBookmarks.class);
-               SessionHistory.HistoryEntry he = getSessionHistory().getHistoryEntryAt(0);
+               Tab tab = Tabs.getInstance().getSelectedTab();
+               Tab.HistoryEntry he = tab.getLastHistoryEntry();
                if (he != null) {
-                intent.setData(android.net.Uri.parse(he.mUri));
-                intent.putExtra("title", he.mTitle);
-                startActivity(intent);
+                   intent.setData(android.net.Uri.parse(he.mUri));
+                   intent.putExtra("title", he.mTitle);
+                   startActivity(intent);
                }
                return true;
+           case R.id.show_tabs:
+               Intent showTabsIntent = new Intent(this, ShowTabs.class);
+               showTabsIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION | Intent.FLAG_ACTIVITY_NO_HISTORY);
+               startActivityForResult(showTabsIntent, SHOW_TABS_REQUEST);
+               return true;
            default:
                return super.onOptionsItemSelected(item);
         }
     }
 
     private void quit() {
         Log.i(LOG_FILE_NAME, "pleaseKillMe");
         if (surfaceView != null)
             surfaceView.saveLast(true);
         System.exit(0);
     }
 
-    SessionHistory getSessionHistory() {
-        return mSessionHistory;
+    public static DatabaseHelper getDatabaseHelper() {
+        if (mDbHelper == null)
+            mDbHelper = new DatabaseHelper(GeckoApp.mAppContext);
+        return mDbHelper;
     }
 
-    void handleLocationChange(final String uri) {
+    void handleLocationChange(final int tabId, final String uri) {
+        Tab tab = Tabs.getInstance().getTab(tabId);
+        if (tab != null)
+            tab.updateURL(uri);
+
+        if (!Tabs.getInstance().isSelectedTab(tab))
+            return;
+
         mMainHandler.post(new Runnable() { 
             public void run() {
                 mAwesomeBar.setText(uri);
             }
         });
     }
 
-    void handleDocumentStart() {
+    void handleDocumentStart(final int tabId) {
+        Tab tab = Tabs.getInstance().getTab(tabId);
+        tab.setLoading(true);
+        
+        if (!Tabs.getInstance().isSelectedTab(tab))
+            return;
+
         mMainHandler.post(new Runnable() { 
             public void run() {
                 mProgressBar.setVisibility(View.VISIBLE);
                 mProgressBar.setIndeterminate(true);
             }
         });
     }
 
-    void handleDocumentStop() {
+    void handleDocumentStop(final int tabId) {
+        Tab tab = Tabs.getInstance().getTab(tabId);
+        tab.setLoading(false);
+        
+        if (!Tabs.getInstance().isSelectedTab(tab))
+            return;
+
         mMainHandler.post(new Runnable() { 
             public void run() {
                 mProgressBar.setVisibility(View.GONE);
                 surfaceView.hideStartupBitmap();
             }
         });
     }
 
-    void handleProgressChange(final int current, final int total) {
+    void handleProgressChange(final int tabId, final int current, final int total) {
+        Tab tab = Tabs.getInstance().getTab(tabId);
+        if (!Tabs.getInstance().isSelectedTab(tab))
+	    return;
+        
         mMainHandler.post(new Runnable() { 
             public void run() {
                 if (total == -1) {
                     mProgressBar.setIndeterminate(true);
                 } else if (current < total) {
                     mProgressBar.setIndeterminate(false);
                     mProgressBar.setMax(total);
                     mProgressBar.setProgress(current);
                 } else {
                     mProgressBar.setIndeterminate(false);
                 }
             }
         });
     }
 
-    void handleContentLoaded(final String uri, final String title) {
+    void handleContentLoaded(final int tabId, final String uri, final String title) {
+        Tab tab = Tabs.getInstance().getTab(tabId);
+        tab.updateTitle(title);
+        tab.addHistory(new Tab.HistoryEntry(uri, title));
+
+        if (!Tabs.getInstance().isSelectedTab(tab))
+	    return;
+
         mMainHandler.post(new Runnable() {
             public void run() {
                 mAwesomeBar.setText(title);
-                mSessionHistory.add(new SessionHistory.HistoryEntry(uri, title));
             }
         });
     }
 
-    void handleTitleChanged(final String title) {
+    void handleTitleChanged(final int tabId, final String title) {
+        Tab tab = Tabs.getInstance().getTab(tabId);
+        tab.updateTitle(title);
+        
+        if (!Tabs.getInstance().isSelectedTab(tab))
+	    return;
+
         mMainHandler.post(new Runnable() { 
             public void run() {
                 mAwesomeBar.setText(title);
             }
         });
     }
 
     void handleLinkAdded(String rel, final String href) {
@@ -553,20 +597,16 @@ abstract public class GeckoApp
 
         surfaceView.loadStartupBitmap();
 
         Log.w(LOGTAG, "zerdatime " + new Date().getTime() + " - UI almost up");
 
         if (sGREDir == null)
             sGREDir = new File(this.getApplicationInfo().dataDir);
 
-        if (mSessionHistory == null) {
-            mSessionHistory = new SessionHistory(this);
-        } // else: keep session history across orientation changes
-
         mMainHandler = new Handler();
 
         if (!sTryCatchAttached) {
             sTryCatchAttached = true;
             mMainHandler.post(new Runnable() {
                 public void run() {
                     try {
                         Looper.loop();
@@ -584,17 +624,17 @@ abstract public class GeckoApp
 
         mMainLayout = (LinearLayout) findViewById(R.id.mainLayout);
         mProgressBar = (ProgressBar) findViewById(R.id.progressBar);
 
         // setup awesome bar
         mAwesomeBar = (Button) findViewById(R.id.awesomeBar);
         mAwesomeBar.setOnClickListener(new EditText.OnClickListener() {
             public void onClick(View v) {
-                onSearchRequested();
+                onEditRequested();
             }
         });
 
         mFavicon = (ImageButton) findViewById(R.id.favimage);
 
         ImageButton reload = (ImageButton) findViewById(R.id.reload);
         reload.setOnClickListener(new ImageButton.OnClickListener() {
             public void onClick(View v) {
@@ -755,18 +795,16 @@ abstract public class GeckoApp
         super.onStart();
     }
 
     @Override
     public void onDestroy()
     {
         Log.i(LOG_FILE_NAME, "destroy");
 
-        mSessionHistory.cleanup();
-
         // Tell Gecko to shutting down; we'll end up calling System.exit()
         // in onXreExit.
         if (isFinishing())
             GeckoAppShell.sendEventToGecko(new GeckoEvent(GeckoEvent.ACTIVITY_SHUTDOWN));
 
         super.onDestroy();
     }
 
@@ -920,29 +958,43 @@ abstract public class GeckoApp
 
         return filePickerResult;
     }
 
     @Override
     public boolean onSearchRequested() {
         Intent searchIntent = new Intent(getBaseContext(), AwesomeBar.class);
         searchIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION | Intent.FLAG_ACTIVITY_NO_HISTORY);
-        mSessionHistory.searchRequested(searchIntent);
+        searchIntent.putExtra(AwesomeBar.TYPE, AwesomeBar.Type.ADD.name());
         startActivityForResult(searchIntent, AWESOMEBAR_REQUEST);
         return true;
     }
+ 
+    public boolean onEditRequested() {
+        Intent intent = new Intent(getBaseContext(), AwesomeBar.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION | Intent.FLAG_ACTIVITY_NO_HISTORY);
+        intent.putExtra(AwesomeBar.TYPE, AwesomeBar.Type.EDIT.name());
+        Tab tab = Tabs.getInstance().getSelectedTab();
+        if (!tab.getHistory().empty()) {
+            intent.putExtra(AwesomeBar.CURRENT_URL_KEY, tab.getHistory().peek().mUri);
+        }
+        startActivityForResult(intent, AWESOMEBAR_REQUEST);
+        return true;
+    }
 
     public boolean doReload() {
         Log.i("GeckoApp", "Reload requested");
-        return mSessionHistory.doReload();
+        Tab tab = Tabs.getInstance().getSelectedTab();
+        return tab.doReload();
     }
 
     @Override
     public void onBackPressed() {
-        if (!mSessionHistory.doBack()) {
+        Tab tab = Tabs.getInstance().getSelectedTab();
+        if (tab == null || !tab.doBack()) {
             finish();
         }
     }
 
     static int kCaptureIndex = 0;
 
     @Override
     protected void onActivityResult(int requestCode, int resultCode,
@@ -999,47 +1051,71 @@ abstract public class GeckoApp
             }
             try {
                 mFilePickerResult.put(filePickerResult);
             } catch (InterruptedException e) {
                 Log.i(LOG_FILE_NAME, "error returning file picker result", e);
             }
             break;
         case AWESOMEBAR_REQUEST:
-
             if (data != null) {
                 String url = data.getStringExtra(AwesomeBar.URL_KEY);
+                AwesomeBar.Type type = AwesomeBar.Type.valueOf(data.getStringExtra(AwesomeBar.TYPE));
                 if (url != null && url.length() > 0) {
                     mProgressBar.setVisibility(View.VISIBLE);
                     mProgressBar.setIndeterminate(true);
-                    loadUrl(url);
+                    loadUrl(url, type);
                 }
             }
-
-
             break;
         case CAMERA_CAPTURE_REQUEST:
             Log.i(LOG_FILE_NAME, "Returning from CAMERA_CAPTURE_REQUEST: " + resultCode);
             File file = new File(Environment.getExternalStorageDirectory(), "cameraCapture-" + Integer.toString(kCaptureIndex) + ".jpg");
             kCaptureIndex++;
             GeckoEvent e = new GeckoEvent("cameraCaptureDone", resultCode == Activity.RESULT_OK ?
                                           "{\"ok\": true,  \"path\": \"" + file.getPath() + "\" }" :
                                           "{\"ok\": false, \"path\": \"" + file.getPath() + "\" }");
             GeckoAppShell.sendEventToGecko(e);
             break;
-        }
+        case SHOW_TABS_REQUEST:
+            if (data != null) {
+                ShowTabs.Type type = ShowTabs.Type.valueOf(data.getStringExtra(ShowTabs.TYPE));
+                if (type == ShowTabs.Type.ADD) {
+                    Intent intent = new Intent(this, AwesomeBar.class);
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION | Intent.FLAG_ACTIVITY_NO_HISTORY);
+                    intent.putExtra(AwesomeBar.TYPE, AwesomeBar.Type.ADD.name());
+                    startActivityForResult(intent, AWESOMEBAR_REQUEST);
+                } else {
+                    int id = Integer.parseInt(data.getStringExtra(ShowTabs.ID));
+                    Tab tab = Tabs.getInstance().switchToTab(id);
+                    if (tab != null) {
+                        mAwesomeBar.setText(tab.getTitle());
+                        if (tab.isLoading())
+                           mProgressBar.setVisibility(View.VISIBLE);
+                        else
+                           mProgressBar.setVisibility(View.GONE);
+                    }
+                    GeckoAppShell.sendEventToGecko(new GeckoEvent("switch-to-tab", "" + id));
+                }
+            }
+       }
     }
 
     public void doCameraCapture() {
         File file = new File(Environment.getExternalStorageDirectory(), "cameraCapture-" + Integer.toString(kCaptureIndex) + ".jpg");
 
         Intent intent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
         intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));
 
         startActivityForResult(intent, CAMERA_CAPTURE_REQUEST);
     }
 
-    public void loadUrl(String url) {
+    public void loadUrl(String url, AwesomeBar.Type type) {
         mAwesomeBar.setText(url);
-        GeckoAppShell.sendEventToGecko(new GeckoEvent(url));
-    }
+        Log.d(LOG_FILE_NAME, type.name());
+        if (type == AwesomeBar.Type.ADD) {
+            GeckoAppShell.sendEventToGecko(new GeckoEvent("add-tab", url));
+        } else {
+            GeckoAppShell.sendEventToGecko(new GeckoEvent("load-tab", url));
+        }
+   }
 
 }
--- a/embedding/android/GeckoAppShell.java
+++ b/embedding/android/GeckoAppShell.java
@@ -1567,58 +1567,68 @@ public class GeckoAppShell
         //                "event_specific": "value",
         //                ....
         try {
             JSONObject json = new JSONObject(message);
             JSONObject geckoObject = json.getJSONObject("gecko");
             String type = geckoObject.getString("type");
 
             if (type.equals("DOMContentLoaded")) {
+                final int tabId = geckoObject.getInt("tabID");
                 final String uri = geckoObject.getString("uri");
                 final String title = geckoObject.getString("title");
                 final CharSequence titleText = title;
-                GeckoApp.mAppContext.handleContentLoaded(uri, title);
+                GeckoApp.mAppContext.handleContentLoaded(tabId, uri, title);
                 Log.i("GeckoShell", "URI - " + uri + ", title - " + title);
             } else if (type.equals("DOMTitleChanged")) {
+                final int tabId = geckoObject.getInt("tabID");
                 final String title = geckoObject.getString("title");
                 final CharSequence titleText = title;
-                GeckoApp.mAppContext.handleTitleChanged(title);
+                GeckoApp.mAppContext.handleTitleChanged(tabId, title);
                 Log.i("GeckoShell", "title - " + title);
             } else if (type.equals("DOMLinkAdded")) {
                 final String rel = geckoObject.getString("rel");
                 final String href = geckoObject.getString("href");
                 Log.i("GeckoShell", "link rel - " + rel + ", href - " + href);
                 GeckoApp.mAppContext.handleLinkAdded(rel, href);
             } else if (type.equals("log")) {
                 // generic log listener
                 final String msg = geckoObject.getString("msg");
                 Log.i("GeckoShell", "Log: " + msg);
             } else if (type.equals("onLocationChange")) {
+                final int tabId = geckoObject.getInt("tabID");
                 final String uri = geckoObject.getString("uri");
                 Log.i("GeckoShell", "URI - " + uri);
-                GeckoApp.mAppContext.handleLocationChange(uri);
+                GeckoApp.mAppContext.handleLocationChange(tabId, uri);
             } else if (type.equals("onStateChange")) {
+                final int tabId = geckoObject.getInt("tabID");
                 int state = geckoObject.getInt("state");
                 Log.i("GeckoShell", "State - " + state);
                 if ((state & WPL_STATE_IS_DOCUMENT) != 0) {
                     if ((state & WPL_STATE_START) != 0) {
                         Log.i("GeckoShell", "Got a document start");
-                        GeckoApp.mAppContext.handleDocumentStart();
+                        GeckoApp.mAppContext.handleDocumentStart(tabId);
                     } else if ((state & WPL_STATE_STOP) != 0) {
                         Log.i("GeckoShell", "Got a document stop");
-                        GeckoApp.mAppContext.handleDocumentStop();
+                        GeckoApp.mAppContext.handleDocumentStop(tabId);
                     }
                 }
             } else if (type.equals("onProgressChange")) {
+                final int tabId = geckoObject.getInt("tabID");
                 final int current = geckoObject.getInt("current");
                 final int total = geckoObject.getInt("total");
 
-                GeckoApp.mAppContext.handleProgressChange(current, total);
+                GeckoApp.mAppContext.handleProgressChange(tabId, current, total);
                 Log.i("GeckoShell", "progress - " + current + "/" + total);
             } else if (type.equals("onCameraCapture")) {
                 //GeckoApp.mAppContext.doCameraCapture(geckoObject.getString("path"));
                 GeckoApp.mAppContext.doCameraCapture();
+            } else if (type.equals("onCreateTab")) {
+                Log.i("GeckoShell", "Created a new tab");
+                int tabId = geckoObject.getInt("tabID");
+                String uri = geckoObject.getString("uri");
+                Tabs.getInstance().addTab(tabId, uri);
             }
         } catch (Exception e) {
             Log.i("GeckoShell", "handleGeckoMessage throws " + e);
         }
     }
 }
--- a/embedding/android/Makefile.in
+++ b/embedding/android/Makefile.in
@@ -52,17 +52,19 @@ JAVAFILES = \
   GeckoEvent.java \
   GeckoSurfaceView.java \
   GeckoInputConnection.java \
   AlertNotification.java \
   SurfaceLockInfo.java \
   AwesomeBar.java \
   DatabaseHelper.java \
   GeckoBookmarks.java \
-  SessionHistory.java \
+  Tab.java \
+  Tabs.java \
+  ShowTabs.java \
   $(NULL)
 
 PROCESSEDJAVAFILES = \
   App.java \
   Restarter.java \
   NotificationHandler.java \
   LauncherShortcuts.java \
   $(NULL)
@@ -131,21 +133,22 @@ RES_LAYOUT = \
   res/layout/notification_icon_text.xml \
   res/layout/launch_app_list.xml \
   res/layout/launch_app_listitem.xml \
   res/layout/awesomebar_search.xml \
   res/layout/awesomebar_row.xml \
   res/layout/gecko_menu.xml \
   res/layout/bookmarks.xml \
   res/layout/bookmark_list_row.xml \
+  res/layout/show_tabs.xml \
   $(NULL)
 
 RES_VALUES = \
 	res/values/colors.xml \
-       res/values/styles.xml \
+        res/values/styles.xml \
 	res/values/themes.xml \
 	$(NULL)
 
 AB_rCD = $(shell echo $(AB_CD) | sed -e s/-/-r/)
 
 JAVA_CLASSPATH = $(ANDROID_SDK)/android.jar
 
 DEFAULT_BRANDPATH = $(DEPTH)/$(MOZ_BRANDING_DIRECTORY)/locales/en-US/brand.dtd
deleted file mode 100644
--- a/embedding/android/SessionHistory.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Mozilla Android code.
- *
- * The Initial Developer of the Original Code is Mozilla Foundation.
- * Portions created by the Initial Developer are Copyright (C) 2009-2011
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *   Brian Nicholson <bnichnolson@mozilla.com>
- *   Kartikaya Gupta <kgupta@mozilla.com>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-package org.mozilla.gecko;
-
-import java.util.Stack;
-
-import android.content.ContentValues;
-import android.content.Intent;
-import android.database.sqlite.SQLiteDatabase;
-import android.os.AsyncTask;
-import android.util.Log;
-
-class SessionHistory
-{
-    private final GeckoApp mApp;
-    private final DatabaseHelper mDbHelper;
-    private final Stack<HistoryEntry> mHistory;
-    private SQLiteDatabase mDb;
-
-    SessionHistory(GeckoApp app) {
-        mApp = app;
-        mDbHelper = new DatabaseHelper(app);
-        mHistory = new Stack<HistoryEntry>();
-    }
-
-    void add(HistoryEntry entry) {
-        new HistoryEntryTask().execute(entry);
-    }
-
-    void searchRequested(Intent searchIntent) {
-        if (!mHistory.empty()) {
-            searchIntent.putExtra(AwesomeBar.CURRENT_URL_KEY, mHistory.peek().mUri);
-        }
-    }
-
-    boolean doReload() {
-        if (mHistory.empty())
-            return false;
-        GeckoEvent e = new GeckoEvent("session-reload", "");
-        GeckoAppShell.sendEventToGecko(e);
-        return true;
-    }
-
-    boolean doBack() {
-        if (mHistory.size() <= 1) {
-            return false;
-        }
-        mHistory.pop();
-        GeckoEvent e = new GeckoEvent("session-back", "");
-        GeckoAppShell.sendEventToGecko(e);
-        return true;
-    }
-
-    void cleanup() {
-        if (mDb != null) {
-            mDb.close();
-        }
-    }
-
-    static class HistoryEntry {
-        public final String mUri;
-        public final String mTitle;
-
-        public HistoryEntry(String uri, String title) {
-            mUri = uri;
-            mTitle = title;
-        }
-    }
-
-    private class HistoryEntryTask extends AsyncTask<HistoryEntry, Void, Void> {
-        protected Void doInBackground(HistoryEntry... entries) {
-            HistoryEntry entry = entries[0];
-            Log.d("GeckoApp", "adding uri=" + entry.mUri + ", title=" + entry.mTitle + " to history");
-            ContentValues values = new ContentValues();
-            values.put("url", entry.mUri);
-            values.put("title", entry.mTitle);
-            if (mHistory.empty() || !mHistory.peek().mUri.equals(entry.mUri))
-                mHistory.push(entry);
-            mDb = mDbHelper.getWritableDatabase();
-            long id = mDb.insertWithOnConflict("moz_places", null, values, SQLiteDatabase.CONFLICT_REPLACE);
-            values = new ContentValues();
-            values.put("place_id", id);
-            mDb.insertWithOnConflict("moz_historyvisits", null, values, SQLiteDatabase.CONFLICT_REPLACE);
-            return null;
-        }
-    }
-    HistoryEntry getHistoryEntryAt(int index) {
-        if (index < mHistory.size())
-            return mHistory.get(index);
-        return null;
-    }
-}
new file mode 100644
--- /dev/null
+++ b/embedding/android/ShowTabs.java
@@ -0,0 +1,121 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Sriram Ramasubramanian <sriram@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko;
+
+import java.io.*;
+import java.util.*;
+
+import org.mozilla.gecko.*;
+
+import android.os.*;
+import android.content.*;
+import android.app.*;
+import android.text.*;
+import android.util.*;
+import android.widget.*;
+import android.database.sqlite.*;
+import android.database.*;
+import android.view.*;
+import android.view.View.*;
+import android.net.Uri;
+import android.graphics.*;
+
+public class ShowTabs extends ListActivity {
+    private static final String LOG_FILE_NAME = "ShowTabs";
+    public static final String ID = "id";
+    public static final String TYPE = "type";
+    private ArrayList <HashMap<String, String>> tabsList = null;
+    public static enum Type { ADD, SWITCH };
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
+        setContentView(R.layout.show_tabs);
+        
+        ListView list = (ListView) findViewById(android.R.id.list);
+        Button addTab = new Button(this);
+        addTab.setText("+ add tab");
+        addTab.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+		Intent resultIntent = new Intent();
+		resultIntent.putExtra(TYPE, Type.ADD.name());
+		setResult(Activity.RESULT_OK, resultIntent);
+                finish();
+            }
+        });
+
+        list.addHeaderView(addTab);
+
+        HashMap<Integer, Tab> tabs = Tabs.getInstance().getTabs();
+        tabsList = new ArrayList<HashMap<String, String>> ();
+
+        if (tabs != null) {
+            Iterator keys = tabs.keySet().iterator();
+            HashMap<String, String> map;
+            Tab tab;
+            while (keys.hasNext()) {
+                tab = tabs.get(keys.next());
+                map = new HashMap<String, String>();
+                map.put("id", "" + tab.getId());
+                map.put("title", tab.getTitle());
+                map.put("url", tab.getURL());
+                tabsList.add(map);
+            }
+        }
+        
+        list.setAdapter(new SimpleAdapter(
+            ShowTabs.this,
+            tabsList,
+            R.layout.awesomebar_row,
+            new String[] { "title", "url" },
+            new int[] { R.id.title, R.id.url }
+        ));
+    }
+
+    @Override
+    public void onListItemClick(ListView l, View v, int position, long id) {
+        HashMap<String, String> map = tabsList.get((int) id); 
+        Intent resultIntent = new Intent();
+        resultIntent.putExtra(TYPE, Type.SWITCH.name());
+        resultIntent.putExtra(ID, map.get("id"));
+        setResult(Activity.RESULT_OK, resultIntent);
+        finish();
+    }
+}
new file mode 100644
--- /dev/null
+++ b/embedding/android/Tab.java
@@ -0,0 +1,179 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009-2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Sriram Ramasubramanian <sriram@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko;
+
+import java.util.*;
+
+import android.content.*;
+import android.database.sqlite.SQLiteDatabase;
+import android.os.AsyncTask;
+import android.graphics.drawable.*;
+import android.util.Log;
+
+public class Tab {
+
+    private static final String LOG_FILE_NAME = "Tab";
+    private int id;
+    private String url, title;
+    private Drawable favicon, thumbnail;
+    private Stack<HistoryEntry> history;
+    private boolean loading;
+
+    static class HistoryEntry {
+        public final String mUri;
+        public final String mTitle;
+
+        public HistoryEntry(String uri, String title) {
+            mUri = uri;
+            mTitle = title;
+        }
+    }
+
+    public Tab() {
+        this.id = -1;
+        this.url = new String();
+        this.title = new String();
+        this.favicon = null;
+        this.thumbnail = null;
+        this.history = new Stack<HistoryEntry>();
+    }
+
+    public Tab(int id, String url) {
+        this.id = id;
+        this.url = new String(url);
+        this.title = new String();
+        this.favicon = null;
+        this.thumbnail = null;
+        this.history = new Stack<HistoryEntry>();
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    public String getURL() {
+        return url;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public boolean isLoading() {
+        return loading;
+    }
+
+    public Stack<HistoryEntry> getHistory() {
+        return history;
+    }
+
+    public void updateURL(String url) {
+        if (!url.isEmpty()) {
+            this.url = new String(url);
+            Log.i(LOG_FILE_NAME, "Updated url: " + url + " for tab with id: " + this.id);
+        }
+    }
+
+    public void updateTitle(String title) {
+        if (!title.isEmpty()) {
+            this.title = new String(title);
+            Log.i(LOG_FILE_NAME, "Updated title: " + title + " for tab with id: " + this.id);
+        }
+    }
+
+    public void setLoading(boolean loading) {
+        this.loading = loading;
+    }
+
+    public void addHistory(HistoryEntry entry) {
+       if (history.empty() || !history.peek().mUri.equals(entry.mUri)) {
+           history.push(entry);
+           new HistoryEntryTask().execute(entry);
+       }
+    }
+
+    public HistoryEntry getLastHistoryEntry() {
+       if (history.empty())
+           return null;
+       return history.peek();
+    }
+
+    public void updateFavicon(Drawable favicon) {
+        if (favicon != null) {
+            this.favicon = favicon;
+            Log.i(LOG_FILE_NAME, "Updated favicon for tab with id: " + this.id);
+        }
+    }
+
+    public boolean doReload() {
+        if (history.empty())
+            return false;
+        GeckoEvent e = new GeckoEvent("session-reload", "");
+        GeckoAppShell.sendEventToGecko(e);
+        return true;
+    }
+
+    public boolean doBack() {
+        if (history.size() <= 1) {
+            return false;
+        }
+        history.pop();
+        GeckoEvent e = new GeckoEvent("session-back", "");
+        GeckoAppShell.sendEventToGecko(e);
+        return true;
+    }
+
+    private class HistoryEntryTask extends AsyncTask<HistoryEntry, Void, Void> {
+        protected Void doInBackground(HistoryEntry... entries) {
+            HistoryEntry entry = entries[0];
+            Log.d(LOG_FILE_NAME, "adding uri=" + entry.mUri + ", title=" + entry.mTitle + " to history");
+            ContentValues values = new ContentValues();
+            values.put("url", entry.mUri);
+            values.put("title", entry.mTitle);
+
+            DatabaseHelper dbHelper = GeckoApp.getDatabaseHelper();
+            SQLiteDatabase mDb = dbHelper.getWritableDatabase();
+            long id = mDb.insertWithOnConflict("moz_places", null, values, SQLiteDatabase.CONFLICT_REPLACE);
+            values = new ContentValues();
+            values.put("place_id", id);
+            mDb.insertWithOnConflict("moz_historyvisits", null, values, SQLiteDatabase.CONFLICT_REPLACE);
+            mDb.close();
+            return null;
+        }
+    }
+} 
new file mode 100644
--- /dev/null
+++ b/embedding/android/Tabs.java
@@ -0,0 +1,121 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Android code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009-2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Sriram Ramasubramanian <sriram@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+package org.mozilla.gecko;
+
+import java.util.*;
+
+import android.graphics.drawable.*;
+import android.util.Log;
+
+public class Tabs {
+
+    private static final String LOG_FILE_NAME = "Tabs";
+    private static int selectedTab = -1;
+    private HashMap<Integer, Tab> tabs;
+
+    private Tabs() {
+        tabs = new HashMap<Integer, Tab>();
+    }
+
+    public int getCount() {
+        return tabs.size();
+    }
+
+    public Tab addTab(int id, String url) {
+        if (tabs.containsKey(id))
+           return tabs.get(id);
+
+        Tab tab = new Tab(id, url);
+        tabs.put(id, tab);
+        Log.i(LOG_FILE_NAME, "Added a tab with id: " + id + ", url: " + url);
+        selectedTab = id;
+        return tab;
+    }
+
+    public void removeTab(int id) {
+        if (tabs.containsKey(id))
+            tabs.remove(id);
+        Log.i(LOG_FILE_NAME, "Removed a tab with id: " + id);
+    }
+
+    public Tab switchToTab(int id) {
+        if (!tabs.containsKey(id))
+            return null;
+ 
+        selectedTab = id;
+        return tabs.get(id);
+    }
+
+    public Tab getSelectedTab() {
+        return tabs.get(selectedTab);
+    }
+
+    public int getSelectedTabId() {
+        return selectedTab;
+    }
+
+    public boolean isSelectedTab(Tab tab) {
+        return (tab.getId() == selectedTab);
+    }
+
+    public Tab getTab(int id) {
+        if (getCount() == 0)
+            return null;
+
+        if (!tabs.containsKey(id))
+           return null;
+
+        return tabs.get(id);
+    }
+
+    public HashMap<Integer, Tab> getTabs() {
+        if (getCount() == 0)
+            return null;
+
+        return tabs;
+    }
+
+    //Making Tabs a singleton class
+    private static class TabsInstanceHolder {
+        private static final Tabs INSTANCE = new Tabs();
+    }
+
+    public static Tabs getInstance() {
+       return Tabs.TabsInstanceHolder.INSTANCE;
+    }
+}
--- a/embedding/android/resources/layout/awesomebar_row.xml
+++ b/embedding/android/resources/layout/awesomebar_row.xml
@@ -10,17 +10,19 @@
                android:layout_height="match_parent"
                android:layout_marginRight="6dip"/>
 
     <TextView android:id="@+id/title"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:textAppearance="?android:attr/textAppearanceMediumInverse"
               android:textColor="?android:attr/textColorPrimaryInverse"
-              android:layout_toRightOf="@id/favicon"/>
+              android:layout_toRightOf="@id/favicon"
+              android:singleLine="true"
+              android:ellipsize="middle"/>
 
     <TextView android:id="@+id/url"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:textAppearance="?android:attr/textAppearanceSmallInverse"
               android:textColor="?android:attr/textColorSecondaryInverse"
               android:layout_marginTop="2dip"
               android:layout_below="@id/title"
--- a/embedding/android/resources/layout/gecko_menu.xml
+++ b/embedding/android/resources/layout/gecko_menu.xml
@@ -1,9 +1,15 @@
 <?xml version="1.0" encoding="utf-8"?>
 <menu xmlns:android="http://schemas.android.com/apk/res/android">
 
     <item android:id="@+id/quit"
           android:icon="@drawable/quit"
           android:title="@string/quit" />
+
     <item android:id="@+id/bookmarks"
           android:title="@string/bookmarks" />
+ 
+    <item android:id="@+id/show_tabs"
+          android:icon="@drawable/quit"
+          android:title="Show Tabs" />
+
 </menu>
new file mode 100644
--- /dev/null
+++ b/embedding/android/resources/layout/show_tabs.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:id="@+id/awesomeScreen"
+              style="@style/Screen">
+
+    <ListView android:id="@android:id/list"
+              android:layout_width="match_parent"
+              android:layout_height="fill_parent"
+              android:layout_weight="1"
+              android:background="#fff"
+              android:cacheColorHint="#fff"
+              android:divider="#666666"
+              android:dividerHeight="1dp"/>
+
+</LinearLayout>
--- a/mobile/chrome/content/browser.js
+++ b/mobile/chrome/content/browser.js
@@ -1,12 +1,13 @@
 let Cc = Components.classes;
 let Ci = Components.interfaces;
 let Cu = Components.utils;
 let Cr = Components.results;
+let gTabIDFactory = 0;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/NetUtil.jsm");
 Cu.import("resource://gre/modules/Services.jsm")
 
 function dump(a) {
   Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).logStringMessage(a);
 }
@@ -25,16 +26,19 @@ var BrowserApp = {
 
   startup: function startup() {
     window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow = new nsBrowserAccess();
     dump("zerdatime " + Date.now() + " - browser chrome startup finished.");
 
     this.deck = document.getElementById("browsers");
     BrowserEventHandler.init();
 
+    Services.obs.addObserver(this, "add-tab", false);
+    Services.obs.addObserver(this, "load-tab", false);
+    Services.obs.addObserver(this, "switch-to-tab", false);
     Services.obs.addObserver(this, "session-back", false);
     Services.obs.addObserver(this, "session-reload", false);
 
     let uri = "about:support";
     try {
       uri = Services.prefs.getCharPref("browser.last.uri");
     } catch (e) {};
   
@@ -64,16 +68,25 @@ var BrowserApp = {
   },
 
   get selectedBrowser() {
     if (this._selectedTab)
       return this._selectedTab.browser;
     return null;
   },
 
+  getTabForId: function getTabForId(aId) {
+    let tabs = this._tabs;
+    for (let i=0; i < tabs.length; i++) {
+       if (tabs[i].id == aId)
+         return tabs[i];
+    }
+    return null;
+  },
+
   getTabForBrowser: function getTabForBrowser(aBrowser) {
     let tabs = this._tabs;
     for (let i = 0; i < tabs.length; i++) {
       if (tabs[i].browser == aBrowser)
         return tabs[i];
     }
     return null;
   },
@@ -117,25 +130,39 @@ var BrowserApp = {
   closeTab: function closeTab(aTab) {
     if (aTab == this.selectedTab)
       this.selectedTab = null;
 
     aTab.destroy();
     this._tabs.splice(this._tabs.indexOf(aTab), 1);
   },
 
+  switchToTab: function switchToTab(aTabId) {
+    let tab = this.getTabForId(aTabId);
+    if (tab != null)
+      this.selectedTab = tab;
+      tab.active = true;
+  },
+
   observe: function(aSubject, aTopic, aData) {
     let browser = this.selectedBrowser;
     if (!browser)
       return;
 
     if (aTopic == "session-back")
       browser.goBack();
     else if (aTopic == "session-reload")
       browser.reload();
+    else if (aTopic == "add-tab") {
+      let newTab = this.addTab(aData);
+      newTab.active = true;
+    } else if (aTopic == "load-tab") 
+      browser.loadURI(aData);
+    else if (aTopic == "switch-to-tab") 
+      this.switchToTab(parseInt(aData));
   }
 }
 
 
 function nsBrowserAccess() {
 }
 
 nsBrowserAccess.prototype = {
@@ -160,34 +187,53 @@ nsBrowserAccess.prototype = {
     return BrowserApp.getBrowserForWindow(aWindow) != null;
   },
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserDOMWindow])
 };
 
 function Tab(aURL) {
   this.browser = null;
+  this._id = 0;
   this.create(aURL);
 }
 
 Tab.prototype = {
+  get id() {
+    return this._id;
+  },
+
+  set id(aId) {
+    this._id = aId;
+  },
+
   create: function(aURL) {
     if (this.browser)
       return;
 
     this.browser = document.createElement("browser");
     this.browser.setAttribute("type", "content");
     BrowserApp.deck.appendChild(this.browser);
     this.browser.stop();
 
     let flags = Ci.nsIWebProgress.NOTIFY_STATE_ALL |
                 Ci.nsIWebProgress.NOTIFY_LOCATION |
                 Ci.nsIWebProgress.NOTIFY_PROGRESS;
     this.browser.addProgressListener(this, flags);
     this.browser.loadURI(aURL);
+    let tabID = this.id = ++gTabIDFactory;
+    let message = {
+      gecko: {
+        type: "onCreateTab",
+        tabID: tabID,
+        uri: aURL
+      }
+    };
+
+    sendMessageToJava(message);
   },
 
   destroy: function() {
     if (!this.browser)
       return;
 
     this.browser.removeProgressListener(this);
     BrowserApp.deck.removeChild(this.browser);
@@ -208,53 +254,67 @@ Tab.prototype = {
 
   get active() {
     if (!this.browser)
       return false;
     return this.browser.getAttribute("type") == "content-primary";
   },
 
   onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) {
+    let browser = BrowserApp.getBrowserForWindow(aWebProgress.DOMWindow);
     let windowID = aWebProgress.DOMWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
+    
+    let uri = "";
+    if (browser)
+      uri = browser.currentURI.spec;
+    let tabID = this.id;
+
     let message = {
       gecko: {
         type: "onStateChange",
+        tabID: tabID,
         windowID: windowID,
+        uri: uri,
         state: aStateFlags
       }
     };
 
     sendMessageToJava(message);
   },
 
   onLocationChange: function(aWebProgress, aRequest, aLocationURI) {
     let browser = BrowserApp.getBrowserForWindow(aWebProgress.DOMWindow);
     let uri = browser.currentURI.spec;
     let windowID = aWebProgress.DOMWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
 
+    let tabID = this.id;
+
     let message = {
       gecko: {
         type: "onLocationChange",
+        tabID: tabID,
         windowID: windowID,
         uri: uri
       }
     };
 
     sendMessageToJava(message);
   },
 
   onSecurityChange: function(aBrowser, aWebProgress, aRequest, aState) {
     dump("progressListener.onSecurityChange");
   },
 
   onProgressChange: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress) {
     let windowID = aWebProgress.DOMWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
+    let tabID = this.id;
     let message = {
       gecko: {
         type: "onProgressChange",
+        tabID: tabID,
         windowID: windowID,
         current: aCurTotalProgress,
         total: aMaxTotalProgress
       }
     };
 
     sendMessageToJava(message);
   },
@@ -282,61 +342,68 @@ var BrowserEventHandler = {
     BrowserApp.deck.addEventListener("DOMLinkAdded", this, true);
     BrowserApp.deck.addEventListener("DOMTitleChanged", this, true);
   },
 
   handleEvent: function(aEvent) {
     switch (aEvent.type) {
       case "DOMContentLoaded": {
         let browser = BrowserApp.getBrowserForDocument(aEvent.target);
+        let tabID = BrowserApp.getTabForBrowser(browser).id;
         let uri = browser.currentURI.spec;
 
         dump("Setting Last uri to: " + uri);
         Services.prefs.setCharPref("browser.last.uri", uri);
 
         sendMessageToJava({
           gecko: {
             type: "DOMContentLoaded",
+            tabID: tabID,
             windowID: 0,
             uri: uri,
             title: browser.contentTitle
           }
         });
         break;
       }
 
-      case "DOMLinkAdded":
+      case "DOMLinkAdded": {
         let target = aEvent.originalTarget;
         if (!target.href || target.disabled)
           return;
-
+        
         let json = {
           type: "DOMLinkAdded",
           windowId: target.ownerDocument.defaultView.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID,
           href: target.href,
           charset: target.ownerDocument.characterSet,
           title: target.title,
           rel: target.rel
         };
         
         // rel=icon can also have a sizes attribute
         if (target.hasAttribute("sizes"))
           json.sizes = target.getAttribute("sizes");
 
         sendMessageToJava({ gecko: json });
         break;
+      }
 
-      case "DOMTitleChanged":
+      case "DOMTitleChanged": {
+        let browser = BrowserApp.getBrowserForDocument(aEvent.target);
+        let tabID = BrowserApp.getTabForBrowser(browser).id;
         sendMessageToJava({
           gecko: {
             type: "DOMTitleChanged",
+            tabID: tabID,
             title: aEvent.target.title
           }
         });
         break;
+      }
 
       case "click":
         if (this.blockClick) {
           aEvent.stopPropagation();
           aEvent.preventDefault();
         }
   
         break;