Bug 504122 - "provide undo close tab". Patch v3. r=asuth
authorThomas Schmid <schmid-thomas@gmx.net>
Sun, 20 Mar 2011 06:56:00 -0700
changeset 7382 711e23c7796083d2658220b5f385e560e192e1d1
parent 7381 e7c212dc0f4e704d0400526e96eebbe8499d3104
child 7383 635f55c596792d23cd57581aed6578f22df12949
push id5661
push userbugmail@asutherland.org
push dateMon, 21 Mar 2011 00:30:07 +0000
treeherdercomm-central@711e23c77960 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersasuth
bugs504122
Bug 504122 - "provide undo close tab". Patch v3. r=asuth
mail/base/content/mail3PaneWindowCommands.js
mail/base/content/mailWindowOverlay.js
mail/base/content/mailWindowOverlay.xul
mail/base/content/tabmail.xml
mail/locales/en-US/chrome/messenger/messenger.dtd
mail/locales/en-US/chrome/messenger/messenger.properties
mail/test/mozmill/tabmail/test-tabmail-dragndrop.js
--- a/mail/base/content/mail3PaneWindowCommands.js
+++ b/mail/base/content/mail3PaneWindowCommands.js
@@ -169,16 +169,17 @@ var DefaultController =
       case "cmd_previousMsg":
       case "cmd_previousUnreadMsg":
       case "cmd_previousFlaggedMsg":
       case "button_goForward":
       case "button_goBack":
       case "cmd_goForward":
       case "cmd_goBack":
       case "cmd_goStartPage":
+      case "cmd_undoCloseTab":
       case "cmd_viewClassicMailLayout":
       case "cmd_viewWideMailLayout":
       case "cmd_viewVerticalMailLayout":
       case "cmd_toggleMessagePane":
       case "cmd_viewAllMsgs":
       case "cmd_viewUnreadMsgs":
       case "cmd_viewThreadsWithUnread":
       case "cmd_viewWatchedThreadsWithUnread":
@@ -423,16 +424,18 @@ var DefaultController =
       case "cmd_goForward":
       case "cmd_goBack":
         if (gDBView)
           enabled.value = gDBView.navigateStatus((command == "cmd_goBack" || command == "button_goBack") ? nsMsgNavigationType.back : nsMsgNavigationType.forward);
         return enabled.value;
       case "cmd_goStartPage":
         return document.getElementById("tabmail").selectedTab.mode.name == "folder" &&
                !IsMessagePaneCollapsed();
+      case "cmd_undoCloseTab":
+        return (document.getElementById("tabmail").recentlyClosedTabs.length > 0);               
       case "cmd_markAllRead":
       case "cmd_markReadByDate":
         return IsFolderSelected();
       case "cmd_find":
       case "cmd_findAgain":
       case "cmd_findPrevious":
         // If we are a message tab, then we've got a message displayed, so
         // always allow searching in the message
@@ -679,16 +682,19 @@ var DefaultController =
       case "button_goBack":
       case "cmd_goBack":
         GoNextMessage(nsMsgNavigationType.back, true);
         break;
       case "cmd_goStartPage":
         HideMessageHeaderPane();
         loadStartPage(true);
         break;
+      case "cmd_undoCloseTab":
+        document.getElementById("tabmail").undoCloseTab();
+        break;
       case "cmd_viewClassicMailLayout":
       case "cmd_viewWideMailLayout":
       case "cmd_viewVerticalMailLayout":
         ChangeMailLayoutForCommand(command);
         break;
       case "cmd_toggleMessagePane":
         MsgToggleMessagePane();
         break;
--- a/mail/base/content/mailWindowOverlay.js
+++ b/mail/base/content/mailWindowOverlay.js
@@ -698,16 +698,64 @@ function InitMessageTags(menuPopup)
     newMenuItem.setAttribute('oncommand', 'ToggleMessageTagMenu(event.target);');
     var color = taginfo.color;
     if (color)
       newMenuItem.setAttribute("class", "lc-" + color.substr(1));
     menuPopup.appendChild(newMenuItem);
   }
 }
 
+function InitRecentlyClosedTabsPopup(menuPopup)
+{
+  let tabs = document.getElementById("tabmail").recentlyClosedTabs;
+
+  // show Popup only when there are restorable tabs.
+  if( !tabs.length )
+    return false;
+
+  // Clear the list before rebulding it.     
+  while (menuPopup.childNodes.length > 0)
+    menuPopup.removeChild(menuPopup.firstChild);
+    
+  // Rebuild the recently closed tab list
+  for (let i = 0; i < tabs.length; i++ ) {
+    
+    let menuItem = document.createElement("menuitem");
+    menuItem.setAttribute("label",tabs[i].title);    
+    menuItem.setAttribute('oncommand',
+        'document.getElementById("tabmail").undoCloseTab('+i+');');
+     
+    if (i==0)
+      menuItem.setAttribute('key',"key_undoCloseTab");
+     
+    menuPopup.appendChild(menuItem);
+  }
+  
+  // "Restore All Tabs" with only one entry does not make sense 
+  if (tabs.length <= 1)
+    return;
+  
+  menuPopup.appendChild(document.createElement("menuseparator"));
+  
+  let menuItem = document.createElement("menuitem");
+  menuItem.setAttribute("label",gMessengerBundle.getString("restoreAllTabs"));
+  menuItem.setAttribute("oncommand","goRestoreAllTabs();");
+  menuPopup.appendChild(menuItem);
+}
+
+function goRestoreAllTabs()
+{
+  let tabmail = document.getElementById("tabmail");
+  
+  let len = tabmail.recentlyClosedTabs.length;
+  
+  while(len--)
+    document.getElementById("tabmail").undoCloseTab();
+}
+
 function backToolbarMenu_init(menuPopup)
 {
   populateHistoryMenu(menuPopup, true);
 }
 
 function getMsgToolbarMenu_init()
 {
   document.commandDispatcher.updateCommands('create-menu-getMsgToolbar');
--- a/mail/base/content/mailWindowOverlay.xul
+++ b/mail/base/content/mailWindowOverlay.xul
@@ -234,16 +234,17 @@
   <command id="cmd_nextMsg" oncommand="goDoCommand('cmd_nextMsg')" disabled="true"/>
   <command id="cmd_nextUnreadMsg" oncommand="goDoCommand('cmd_nextUnreadMsg')" disabled="true"/>
   <command id="cmd_nextFlaggedMsg" oncommand="goDoCommand('cmd_nextFlaggedMsg')" disabled="true"/>
   <command id="cmd_nextUnreadThread" oncommand="goDoCommand('cmd_nextUnreadThread')" disabled="true"/>
   <command id="cmd_previousMsg" oncommand="goDoCommand('cmd_previousMsg')" disabled="true"/>
   <command id="cmd_previousUnreadMsg" oncommand="goDoCommand('cmd_previousUnreadMsg')" disabled="true"/>
   <command id="cmd_previousFlaggedMsg" oncommand="goDoCommand('cmd_previousFlaggedMsg')" disabled="true"/>
   <command id="cmd_goStartPage" oncommand="goDoCommand('cmd_goStartPage');"/>
+  <command id="cmd_undoCloseTab" oncommand="goDoCommand('cmd_undoCloseTab');"/> 
   <command id="cmd_goForward" oncommand="goDoCommand('cmd_goForward')" disabled="true"/>
   <command id="cmd_goBack" oncommand="goDoCommand('cmd_goBack')" disabled="true"/>
 </commandset>
 
 <commandset id="mailMessageMenuItems"
             commandupdater="true"
             events="create-menu-message"
             oncommandupdate="goUpdateMailMenuItems(this)">
@@ -404,16 +405,17 @@
   <key key="&collapseAllThreadsCmd.key;" modifiers="shift"           oncommand="goDoCommand('cmd_collapseAllThreads')"/>
   <key id="key_nextUnreadThread" key="&nextUnreadThread.key;"        oncommand="goDoCommand('cmd_nextUnreadThread')"/>
   <key id="key_previousMsg" key="&prevMsgCmd.key;"                   oncommand="goDoCommand('cmd_previousMsg')"/>
   <key id="key_previousUnreadMsg" key="&prevUnreadMsgCmd.key;"       oncommand="goDoCommand('cmd_previousUnreadMsg')"/>
   <key id="key_archive" key="&archiveMsgCmd.key;"                    oncommand="goDoCommand('cmd_archive')"/>
   <key id="key_goForward" key="&goForwardCmd.commandKey;"            oncommand="goDoCommand('cmd_goForward')"/>
   <key id="key_goBack" key="&goBackCmd.commandKey;"                  oncommand="goDoCommand('cmd_goBack')"/>
   <key id="key_goStartPage" keycode="VK_HOME"                        oncommand="goDoCommand('cmd_goStartPage')" modifiers="alt"/>
+  <key id="key_undoCloseTab" key="&undoCloseTabCmd.commandkey;"      oncommand="goDoCommand('cmd_undoCloseTab')" modifiers="accel, shift"/> 
   <key id="key_reply" key="&replyMsgCmd.key;"                        oncommand="goDoCommand('cmd_reply')" modifiers="accel"/>
   <key id="key_replyall" key="&replyToAllMsgCmd.key;"                oncommand="goDoCommand('cmd_replyall')" modifiers="accel, shift"/>
   <key id="key_replylist" key="&replyToListMsgCmd.key;"              oncommand="goDoCommand('cmd_replylist')" modifiers="accel, shift"/>
   <key id="key_forward" key="&forwardMsgCmd.key;"                    oncommand="goDoCommand('cmd_forward')" modifiers="accel"/>
   <key id="key_editAsNew" key="&editMsgAsNewCmd.key;"                oncommand="goDoCommand('cmd_editAsNew')" modifiers="accel"/>
   <key id="key_watchThread" key="&watchThreadMenu.key;"              oncommand="goDoCommand('cmd_watchThread')" />
   <key id="key_killThread" key="&killThreadMenu.key;"                oncommand="goDoCommand('cmd_killThread')" />
   <key id="key_killSubthread" key="&killSubthreadMenu.key;"          oncommand="goDoCommand('cmd_killSubthread')" modifiers="shift" />
@@ -1391,16 +1393,24 @@
                        showFileHereLabel="true"
                        fileHereLabel="&thisFolder.label;"
                        fileHereAccessKey="&thisFolder.accesskey;"
                        showRecent="true"
                        recentLabel="&contextMoveCopyMsgRecentMenu.label;"
                        recentAccessKey="&contextMoveCopyMsgRecentMenu.accesskey;"/>
           </menu>
           <menuseparator id="goFolderSeparator"/>
+          
+          <menu id="goRecentlyClosedTabs"
+                label="&goRecentlyClosedTabs.label;">
+            <menupopup id="menu_GoRecentlyClosedTabsPopup"
+                       onpopupshowing="return InitRecentlyClosedTabsPopup(this)" />
+          </menu>
+          <menuseparator id="goRecentlyClosedTabsSeparator"/>
+               
           <menuitem id="goStartPage"
                     label="&startPageCmd.label;"
                     accesskey="&startPageCmd.accesskey;"
                     command="cmd_goStartPage"
                     key="key_goStartPage"/>
         </menupopup>
         </menu>
 
--- a/mail/base/content/tabmail.xml
+++ b/mail/base/content/tabmail.xml
@@ -314,20 +314,21 @@
             
             <xul:menuitem label="&closeOtherTabsCmd2.label;" 
                           accesskey="&closeOtherTabsCmd2.accesskey;"
                           anonid="closeOtherTabs"
                           oncommand="document.getElementById('tabmail').closeOtherTabs(document.popupNode);"/>
                                                  
             <xul:menuseparator />
             
-            <xul:menuitem label="&undoCloseTabCmd.label;"
-                          accesskey="&undoCloseTabCmd.accesskey;"
-                          anonid="undoCloseTab"
-                          oncommand="document.getElementById('tabmail').undoCloseTab();"/>
+            <xul:menu label="&recentlyClosedTabsCmd.label;"
+                      accesskey="&recentlyClosedTabsCmd.accesskey;"
+                      anonid="recentlyClosedTabs" >
+              <xul:menupopup onpopupshowing="return InitRecentlyClosedTabsPopup(this);" />
+            </xul:menu>
                                                                          
             <xul:menuitem label="&closeTabCmd2.label;" 
                           accesskey="&closeTabCmd2.accesskey;"
                           anonid="closeTab"
                           oncommand="document.getElementById('tabmail').closeTab(document.popupNode);"/>
                                                                           
           </xul:menupopup>
           <xul:tabs class="tabmail-tabs" flex="1"
@@ -344,17 +345,16 @@
         <children includes="tabpanels"/>
       </xul:tabbox>
     </content>
 
     <implementation implements="nsIController">
       <constructor>
         window.controllers.insertControllerAt(0, this);
         this._restoringTabState = null;
-        this._tabUndo = [];
       </constructor>
       <destructor>
         window.controllers.removeController(this);
       </destructor>
       <field name="currentTabInfo">
         null
       </field>
       <!-- Temporary field that only has a non-null value during a call to
@@ -382,16 +382,19 @@
         document.getAnonymousElementByAttribute(this, "anonid", "tabcontainer");
       </field>
       <field name="panelContainer" readonly="true">
         document.getElementById(this.getAttribute("panelcontainer"));
       </field>
       <field name="tabMonitors" readonly="true">
         new Array()
       </field>
+      <field name="recentlyClosedTabs" readonly="true">
+        new Array();
+      </field>      
       <method name="registerTabType">
         <parameter name="aTabType"/>
         <body><![CDATA[
           if (aTabType.name in this.tabTypes)
             return;
         
           this.tabTypes[aTabType.name] = aTabType;
           for (let [modeName, modeDetails] in Iterator(aTabType.modes)) {
@@ -668,18 +671,27 @@
             return tabToConsider;
           else if (aTabMode.tabs.length)
             return aTabMode.tabs[0];
           else
             return null;
         ]]></body>
       </method>
       <method name="undoCloseTab">
+        <parameter name="aIdx"/>
         <body><![CDATA[
-          let history = this._tabUndo.pop();
+            
+          if (!this.recentlyClosedTabs.length)
+            return;
+        
+          if (aIdx >= this.recentlyClosedTabs.length)
+            aIdx = this.recentlyClosedTabs.length-1;
+
+          // splice always returns an array 
+          let history = (this.recentlyClosedTabs.splice(aIdx,1))[0];
           
           if (!history.tab)
             return;
 
           if (!this.restoreTab(JSON.parse(history.tab)))
             return;
           
           let idx = Math.min(history.idx,this.tabInfo.length);          
@@ -706,21 +718,22 @@
                 tabMonitor.onTabClosing(tab);
             }
 
             if (!aNoUndo) {
               // Allow user to undo accidentially closed tabs
               let session = this.persistTab(tab);
 
               if (session) {
-                this._tabUndo.push(
-                    { tab: JSON.stringify(session), idx: iTab } );
+              
+                this.recentlyClosedTabs.unshift(
+                  { tab: JSON.stringify(session), idx: iTab, title: tab.title });
 
-                if (this._tabUndo.length > 10)
-                  this._tabUndo.shift();    
+                if (this.recentlyClosedTabs.length > 10)
+                  this.recentlyClosedTabs.pop();    
               }
             }
             
             let closeFunc = tab.mode.closeTab || tab.mode.tabType.closeTab;
             closeFunc.call(tab.mode.tabType, tab);
              
             this.tabInfo.splice(iTab, 1);
             tab.mode.tabs.splice(tab.mode.tabs.indexOf(tab), 1);
@@ -1322,19 +1335,19 @@
             // ... while moving it to a new window.
             document
               .getAnonymousElementByAttribute(this, "anonid", "openTabInWindow")
               .setAttribute("disabled", 
                   (tab.canClose && this.persistTab(tab)) ? "false" : "true")
 
             // If the tab history is empty, disable "Undo Close Tab"
             document
-              .getAnonymousElementByAttribute(this, "anonid", "undoCloseTab")
+              .getAnonymousElementByAttribute(this, "anonid", "recentlyClosedTabs")
               .setAttribute("disabled",
-                  (this._tabUndo.length) ? "false" : "true");
+                  (this.recentlyClosedTabs.length) ? "false" : "true");
                           
             return true;
           ]]>
         </body>
       </method>
       <method name="supportsCommand">
         <parameter name="aCommand"/>
         <body>
--- a/mail/locales/en-US/chrome/messenger/messenger.dtd
+++ b/mail/locales/en-US/chrome/messenger/messenger.dtd
@@ -7,18 +7,20 @@
 <!ENTITY newFolderCmd.accesskey "F">
 <!ENTITY closeTabCmd2.label "Close Tab">
 <!ENTITY closeTabCmd2.accesskey "C">
 <!ENTITY closeOtherTabsCmd2.label "Close Other Tabs">
 <!ENTITY closeOtherTabsCmd2.accesskey "o">
 <!-- LOCALIZATION NOTE (undoCloseTabCmd.label):
      Menu option to attempt to re-open a recently closed tab.
      -->
-<!ENTITY undoCloseTabCmd.label "Undo Close Tab">
-<!ENTITY undoCloseTabCmd.accesskey "U">
+<!ENTITY recentlyClosedTabsCmd.label "Recently Closed Tabs">
+<!ENTITY recentlyClosedTabsCmd.accesskey "R">
+
+<!ENTITY undoCloseTabCmd.commandkey "T">
 <!-- LOCALIZATION NOTE (moveToNewWindow.label):
      Menu option to cause the current tab to be migrated to a new Thunderbird
      window.
      -->
 <!ENTITY moveToNewWindow.label "Move to New Window">
 <!ENTITY moveToNewWindow.accesskey "W">
 <!ENTITY newVirtualFolderCmd.label "Saved Search…">
 <!ENTITY newVirtualFolderCmd.accesskey "S">
@@ -319,16 +321,17 @@ you can use these alternative items. Oth
 <!ENTITY goBackCmd.accesskey "B">
 <!ENTITY goBackCmd.commandKey "[">
 <!ENTITY prevStarredMsgCmd.label "Starred Message">
 <!ENTITY prevStarredMsgCmd.accesskey "S">
 <!ENTITY folderMenu.label "Folder">
 <!ENTITY folderMenu.accesskey "O">
 <!ENTITY thisFolder.label "This Folder">
 <!ENTITY thisFolder.accesskey "F">
+<!ENTITY goRecentlyClosedTabs.label "Recently Closed Tabs">
 <!ENTITY startPageCmd.label "Mail Start Page">
 <!ENTITY startPageCmd.accesskey "S">
 
 <!-- Message Menu -->
 <!ENTITY msgMenu.label "Message">
 <!ENTITY msgMenu.accesskey "M">
 <!ENTITY newMsgCmd.label "New Message">
 <!ENTITY newMsgCmd.accesskey "N">
--- a/mail/locales/en-US/chrome/messenger/messenger.properties
+++ b/mail/locales/en-US/chrome/messenger/messenger.properties
@@ -324,16 +324,17 @@ newsAcctType=News
 
 # LOCALIZATION NOTE(nocachedbodytitle): Do not translate "<TITLE>" or "</TITLE>" in the line below
 nocachedbodytitle=<TITLE>Go Online to View This Message</TITLE>\n
 
 # mailWindowOverlay.js
 confirmUnsubscribeTitle=Confirm Unsubscribe
 confirmUnsubscribeText=Are you sure you want to unsubscribe from %S?
 confirmUnsubscribeManyText=Are you sure you want to unsubscribe from these newsgroups?
+restoreAllTabs=Restore All Tabs
 
 # msgHdrViewOverlay.js
 openLabel=Open
 openLabelAccesskey=O
 saveLabel=Save As…
 saveLabelAccesskey=A
 detachLabel=Detach…
 detachLabelAccesskey=D
--- a/mail/test/mozmill/tabmail/test-tabmail-dragndrop.js
+++ b/mail/test/mozmill/tabmail/test-tabmail-dragndrop.js
@@ -29,30 +29,33 @@
  * 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 ***** */
 
+var elib = {};
+Cu.import('resource://mozmill/modules/elementslib.js', elib);
+
 /*
  * Test rearanging tabs via drag'n'drop.
  */
 
 var MODULE_NAME = "test-tabmail-dragndrop";
 
 var RELATIVE_ROOT = "../shared-modules";
 var MODULE_REQUIRES = ["folder-display-helpers", "window-helpers"];
 
 var folder;
 let msgHdrsInFolder = [];
 
 // The number of messages in folder.
-const NUM_MESSAGES_IN_FOLDER = 10;
+const NUM_MESSAGES_IN_FOLDER = 15;
 
 function setupModule(module) {
   let fdh = collector.getModule("folder-display-helpers");
   fdh.installInto(module);
   let wh = collector.getModule("window-helpers");
   wh.installInto(module);
 
   folder = create_folder("MessageFolder");
@@ -98,282 +101,392 @@ function test_tab_reorder_setup_globals(
  * movable tab and drops it onto the third movable tab.
  */
 function test_tab_reorder_tabbar(){
 
   // Ensure only one tab is open, otherwise our test most likey fail anyway.
   mc.tabmail.closeOtherTabs(0);
   assert_number_of_tabs_open(1);
 
-  try {
-
-    be_in_folder(folder);
+  be_in_folder(folder);
 
-    // Open four tabs
-    for (let idx=0; idx < 4 ; idx++) {
-      select_click_row(idx);
-      open_selected_message_in_new_tab(true);
-    }
+  // Open four tabs
+  for (let idx=0; idx < 4 ; idx++) {
+    select_click_row(idx);
+    open_selected_message_in_new_tab(true);
+  }
 
-    // Check if every thing is correctly initalized
-    assert_number_of_tabs_open(5);
+  // Check if every thing is correctly initalized
+  assert_number_of_tabs_open(5);
 
-    assert_true(mc.tabmail.tabModes["message"].tabs[0] == mc.tabmail.tabInfo[1],
-        " tabMode.tabs and tabInfo out of sync");
+  assert_true(mc.tabmail.tabModes["message"].tabs[0] == mc.tabmail.tabInfo[1],
+      " tabMode.tabs and tabInfo out of sync");
 
-    assert_true(mc.tabmail.tabModes["message"].tabs[1] == mc.tabmail.tabInfo[2],
-        " tabMode.tabs and tabInfo out of sync");
+  assert_true(mc.tabmail.tabModes["message"].tabs[1] == mc.tabmail.tabInfo[2],
+      " tabMode.tabs and tabInfo out of sync");
 
-    assert_true(mc.tabmail.tabModes["message"].tabs[2] == mc.tabmail.tabInfo[3],
-        " tabMode.tabs and tabInfo out of sync");
+  assert_true(mc.tabmail.tabModes["message"].tabs[2] == mc.tabmail.tabInfo[3],
+      " tabMode.tabs and tabInfo out of sync");
 
-    // Start dragging the first tab
-    switch_tab(1);
-    assert_selected_and_displayed(msgHdrsInFolder[0]);
+  // Start dragging the first tab
+  switch_tab(1);
+  assert_selected_and_displayed(msgHdrsInFolder[0]);
 
-    let tab1 = mc.tabmail.tabContainer.childNodes[1];
-    let tab3 = mc.tabmail.tabContainer.childNodes[3];
+  let tab1 = mc.tabmail.tabContainer.childNodes[1];
+  let tab3 = mc.tabmail.tabContainer.childNodes[3];
 
-    let dt = _synthesizeDragStart(mc.window, tab1, mc.tabmail);
+  let dt = _synthesizeDragStart(mc.window, tab1, mc.tabmail);
 
-    // Drop it onto the third tab ...
-    _synthesizeDragOver(mc.window, tab3, dt);
+  // Drop it onto the third tab ...
+  _synthesizeDragOver(mc.window, tab3, dt);
 
-    _synthesizeDrop(mc.window, tab3, dt,
-        { screenX : tab3.boxObject.screenX + (tab3.boxObject.width * 0.75),
-          screenY : tab3.boxObject.screenY });
+  _synthesizeDrop(mc.window, tab3, dt,
+      { screenX : tab3.boxObject.screenX + (tab3.boxObject.width * 0.75),
+        screenY : tab3.boxObject.screenY });
 
-    wait_for_message_display_completion(mc);
+  wait_for_message_display_completion(mc);
 
-    // if every thing went well...
-    assert_number_of_tabs_open(5);
+  // if every thing went well...
+  assert_number_of_tabs_open(5);
 
-    // ... we should find tab1 at the third position...
-    assert_true(tab1 == mc.tabmail.tabContainer.childNodes[3],
-                "Moving tab1 failed");
-    switch_tab(3);
-    assert_selected_and_displayed(msgHdrsInFolder[0]);
+  // ... we should find tab1 at the third position...
+  assert_true(tab1 == mc.tabmail.tabContainer.childNodes[3],
+              "Moving tab1 failed");
+  switch_tab(3);
+  assert_selected_and_displayed(msgHdrsInFolder[0]);
 
-    // ... while tab3 moves one up and gets second.
-    assert_true(tab3 == mc.tabmail.tabContainer.childNodes[2],
-                "Moving tab3 failed");
-    switch_tab(2);
-    assert_selected_and_displayed(msgHdrsInFolder[2]);
-
-    // we have one "message" tab and three "folder" tabs, thus tabInfo[1-3] and
-    // tabMode["message"].tabs[0-2] have to be same, otherwise something went
-    // wrong while moving tabs around
-    assert_true(mc.tabmail.tabModes["message"].tabs[0] == mc.tabmail.tabInfo[1],
-        " tabMode.tabs and tabInfo out of sync");
+  // ... while tab3 moves one up and gets second.
+  assert_true(tab3 == mc.tabmail.tabContainer.childNodes[2],
+              "Moving tab3 failed");
+  switch_tab(2);
+  assert_selected_and_displayed(msgHdrsInFolder[2]);
 
-    assert_true(mc.tabmail.tabModes["message"].tabs[1] == mc.tabmail.tabInfo[2],
-        " tabMode.tabs and tabInfo out of sync");
+  // we have one "message" tab and three "folder" tabs, thus tabInfo[1-3] and
+  // tabMode["message"].tabs[0-2] have to be same, otherwise something went
+  // wrong while moving tabs around
+  assert_true(mc.tabmail.tabModes["message"].tabs[0] == mc.tabmail.tabInfo[1],
+      " tabMode.tabs and tabInfo out of sync");
 
-    assert_true(mc.tabmail.tabModes["message"].tabs[2] == mc.tabmail.tabInfo[3],
-        " tabMode.tabs and tabInfo out of sync");
-  }
-  finally {
-    // finally close the tabs we opened.
-    mc.tabmail.closeOtherTabs(0);
-    assert_number_of_tabs_open(1);
-  }
+  assert_true(mc.tabmail.tabModes["message"].tabs[1] == mc.tabmail.tabInfo[2],
+      " tabMode.tabs and tabInfo out of sync");
+
+  assert_true(mc.tabmail.tabModes["message"].tabs[2] == mc.tabmail.tabInfo[3],
+      " tabMode.tabs and tabInfo out of sync");
 }
 
 /**
  * Tests drag'n'drop tab reordering between windows
  */
 function test_tab_reorder_window(){
 
   // Ensure only one tab is open, otherwise our test most likey fail anyway.
   mc.tabmail.closeOtherTabs(0);
   assert_number_of_tabs_open(1);
 
   let mc2 = null;
 
-  try {
-
-    be_in_folder(folder);
+  be_in_folder(folder);
 
-    // Open a new tab...
-    select_click_row(1);
-    open_selected_message_in_new_tab(false);
+  // Open a new tab...
+  select_click_row(1);
+  open_selected_message_in_new_tab(false);
 
-    assert_number_of_tabs_open(2);
+  assert_number_of_tabs_open(2);
+
+  switch_tab(1);
+  assert_selected_and_displayed(msgHdrsInFolder[1]);
 
-    switch_tab(1);
-    assert_selected_and_displayed(msgHdrsInFolder[1]);
-
-    // ...and then a new 3 pane as our drop target.
-    plan_for_new_window("mail:3pane");
+  // ...and then a new 3 pane as our drop target.
+  plan_for_new_window("mail:3pane");
 
-    let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"]
-                          .getService(Ci.nsIWindowWatcher);
+  let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"]
+                        .getService(Ci.nsIWindowWatcher);
 
-    let args = {msgHdr: msgHdrsInFolder[3]};
-    args.wrappedJSObject = args;
+  let args = {msgHdr: msgHdrsInFolder[3]};
+  args.wrappedJSObject = args;
 
-    let aWnd2 = ww.openWindow(null,
+  let aWnd2 = ww.openWindow(null,
         "chrome://messenger/content/", "",
         "all,chrome,dialog=no,status,toolbar", args);
 
-    mc2 = wait_for_new_window("mail:3pane");
-    wait_for_message_display_completion(mc2,true);
+  mc2 = wait_for_new_window("mail:3pane");
+  wait_for_message_display_completion(mc2,true);
 
-    // Double check if we are listening to the right window.
-    assert_true(aWnd2 == mc2.window, "Opening Window failed" );
-
-    // Start dragging the first tab ...
-    let tabA = mc.tabmail.tabContainer.childNodes[1];
-    assert_true(tabA, "No movable Tab");
+  // Double check if we are listening to the right window.
+  assert_true(aWnd2 == mc2.window, "Opening Window failed" );
 
-    // We drop onto the Folder Tab, it is guaranteed to exist.
-    let tabB = mc2.tabmail.tabContainer.childNodes[0];
-    assert_true(tabB, "No movable Tab");
-
-    let dt = _synthesizeDragStart(mc.window,tabA,mc.tabmail);
+  // Start dragging the first tab ...
+  let tabA = mc.tabmail.tabContainer.childNodes[1];
+  assert_true(tabA, "No movable Tab");
 
-    _synthesizeDragOver(mc2.window, tabB,dt);
+  // We drop onto the Folder Tab, it is guaranteed to exist.
+  let tabB = mc2.tabmail.tabContainer.childNodes[0];
+  assert_true(tabB, "No movable Tab");
 
-    _synthesizeDrop(mc2.window,tabB, dt,
-        { screenX : tabB.boxObject.screenX + (tabB.boxObject.width * 0.75),
-          screenY : tabB.boxObject.screenY });
+  let dt = _synthesizeDragStart(mc.window,tabA,mc.tabmail);
 
-    wait_for_message_display_completion(mc2);
-
-    assert_true( !! (mc.tabmail.tabContainer.childNodes.length == 1),
-      "Moving tab to new window failed, tab still in old window");
+  _synthesizeDragOver(mc2.window, tabB,dt);
 
-    assert_true( !! (mc2.tabmail.tabContainer.childNodes.length == 2),
-      "Moving tab to new window failed, no new tab in new window");
+  _synthesizeDrop(mc2.window,tabB, dt,
+      { screenX : tabB.boxObject.screenX + (tabB.boxObject.width * 0.75),
+        screenY : tabB.boxObject.screenY });
 
-    assert_selected_and_displayed(mc2,msgHdrsInFolder[1]);
+  wait_for_message_display_completion(mc2);
 
-  }
-  finally {
-    // finally close the tabs and windows we opened.
-    mc.tabmail.closeOtherTabs(0);
-    assert_number_of_tabs_open(1);
+  assert_true( !! (mc.tabmail.tabContainer.childNodes.length == 1),
+    "Moving tab to new window failed, tab still in old window");
 
-    if (mc2)
-      mc2.window.close();
-  }
+  assert_true( !! (mc2.tabmail.tabContainer.childNodes.length == 2),
+    "Moving tab to new window failed, no new tab in new window");
 
-
+  assert_selected_and_displayed(mc2,msgHdrsInFolder[1]);
 }
 
 /**
  * Tests detaching tabs into windows via drag'n'drop
  */
 function test_tab_reorder_detach(){
 
   // Ensure only one tab is open, otherwise our test most likey fail anyway.
   mc.tabmail.closeOtherTabs(0);
   assert_number_of_tabs_open(1);
 
   let mc2 = null;
 
-  try {
-
-    be_in_folder(folder);
+  be_in_folder(folder);
 
-    // Open a new tab...
-    select_click_row(2);
-    open_selected_message_in_new_tab(false);
+  // Open a new tab...
+  select_click_row(2);
+  open_selected_message_in_new_tab(false);
 
-    assert_number_of_tabs_open(2);
+  assert_number_of_tabs_open(2);
 
-    // ... if every thing works we should expect a new window...
-    plan_for_new_window("mail:3pane");
+  // ... if every thing works we should expect a new window...
+  plan_for_new_window("mail:3pane");
 
-    // ... now start dragging
-
-    mc.tabmail.switchToTab(1);
+  // ... now start dragging
 
-    let tab1 = mc.tabmail.tabContainer.childNodes[1];
-    let dropContent = mc.e("tabpanelcontainer");
-    let box = dropContent.boxObject;
+  mc.tabmail.switchToTab(1);
 
-    let dt = _synthesizeDragStart(mc.window, tab1, mc.tabmail);
-
-    _synthesizeDragOver(mc.window, dropContent, dt);
+  let tab1 = mc.tabmail.tabContainer.childNodes[1];
+  let dropContent = mc.e("tabpanelcontainer");
+  let box = dropContent.boxObject;
 
-    // notify tab1 drag has ended
-    _synthesizeDragEnd(mc.window, dropContent, tab1, dt,
-        { screenX : (box.screenX + box.width / 2 ),
-          screenY : (box.screenY + box.height / 2 ) });
+  let dt = _synthesizeDragStart(mc.window, tab1, mc.tabmail);
+
+  _synthesizeDragOver(mc.window, dropContent, dt);
 
-    // ... and wait for the new window
-    mc2 = wait_for_new_window("mail:3pane");
-    wait_for_message_display_completion(mc2, true);
-
-    assert_true(mc.tabmail.tabContainer.childNodes.length == 1,
-        "Moving tab to new window failed, tab still in old window");
+  // notify tab1 drag has ended
+  _synthesizeDragEnd(mc.window, dropContent, tab1, dt,
+      { screenX : (box.screenX + box.width / 2 ),
+        screenY : (box.screenY + box.height / 2 ) });
 
-    assert_true(mc2.tabmail.tabContainer.childNodes.length == 2,
-        "Moving tab to new window failed, no new tab in new window");
-
-    assert_selected_and_displayed(mc2, msgHdrsInFolder[2]);
+  // ... and wait for the new window
+  mc2 = wait_for_new_window("mail:3pane");
+  wait_for_message_display_completion(mc2, true);
 
-  }
-  finally {
-    // finally close the tabs and window we opened.
-    mc.tabmail.closeOtherTabs(0);
-    assert_number_of_tabs_open(1);
+  assert_true(mc.tabmail.tabContainer.childNodes.length == 1,
+      "Moving tab to new window failed, tab still in old window");
 
-    if (mc2)
-      mc2.window.close();
-  }
+  assert_true(mc2.tabmail.tabContainer.childNodes.length == 2,
+      "Moving tab to new window failed, no new tab in new window");
+
+  assert_selected_and_displayed(mc2, msgHdrsInFolder[2]);
+
 }
 
 /**
  * Test undo of recently closed tabs.
  */
 function test_tab_undo() {
   // Ensure only one tab is open, otherwise our test most likey fail anyway.
   mc.tabmail.closeOtherTabs(0);
   assert_number_of_tabs_open(1);
 
-  try {
+  be_in_folder(folder);
+
+  // Open five tabs...
+  for (let idx = 0; idx < 5; idx++) {
+    select_click_row(idx);
+    open_selected_message_in_new_tab(true);
+  }
+
+  assert_number_of_tabs_open(6);
+
+  switch_tab(2);
+  assert_selected_and_displayed(msgHdrsInFolder[1]);
 
-    be_in_folder(folder);
+  mc.tabmail.closeTab(2);
+  // This tab should not be added to recently closed tabs...
+  // ... thus it can't be restored
+  mc.tabmail.closeTab(2, true);
+  mc.tabmail.closeTab(2);
+
+  assert_number_of_tabs_open(3);
+  assert_selected_and_displayed(mc, msgHdrsInFolder[4]);
+
+  mc.tabmail.undoCloseTab();
+  assert_number_of_tabs_open(4);
+  assert_selected_and_displayed(mc, msgHdrsInFolder[3]);
+
+  // msgHdrsInFolder[2] won't be restorend it was closed with disabled undo.
+
+  mc.tabmail.undoCloseTab();
+  assert_number_of_tabs_open(5);
+  assert_selected_and_displayed(mc, msgHdrsInFolder[1]);
+}
 
-    // Open five tabs...
-    for (let idx = 0; idx < 5; idx++) {
-      select_click_row(idx);
-      open_selected_message_in_new_tab(true);
-    }
+function _synthesizeRecentlyClosedMenu()
+{                  
+  mc.rightClick(new elib.Elem(mc.tabmail.tabContainer.childNodes[1]));
+  
+  wait_for_popup_to_open(
+    mc.window.document.getAnonymousElementByAttribute(
+      mc.tabmail,"anonid","tabContextMenu"));
+      
+  let menu = mc.window.document.getAnonymousElementByAttribute(
+                   mc.tabmail,"anonid","recentlyClosedTabs");      
+
+  EventUtils.synthesizeMouse(menu,5, 5, {},mc.window);
+  wait_for_popup_to_open(menu.menupopup);
+  
+  return menu;
+}
+
+function _teardownRecentlyClosedMenu()
+{  
+  let menu = mc.window.document.getAnonymousElementByAttribute(
+            mc.tabmail,"anonid","tabContextMenu")  
+  close_popup(mc,new elib.Elem(menu));
+}
 
-    assert_number_of_tabs_open(6);
+/**
+ * Tests the recently closed tabs menu. 
+ */
+function test_tab_recentlyClosed() {
 
-    switch_tab(2);
-    assert_selected_and_displayed(msgHdrsInFolder[1]);
+  // Ensure only one tab is open, otherwise our test most likey fail anyway.
+  mc.tabmail.closeOtherTabs(0);
+  assert_number_of_tabs_open(1);
+  
+  // We start with a clean tab history.
+  mc.tabmail.recentlyClosedTabs = [];
+        
+  // The history is cleaned so let's open 15 tabs...
+  be_in_folder(folder);    
+                   
+  for (let idx = 0; idx < 15; idx++) {
+    select_click_row(idx);
+    open_selected_message_in_new_tab(true);
+  }
 
-    mc.tabmail.closeTab(2);
-    mc.tabmail.closeTab(2, true);
-    mc.tabmail.closeTab(2);
+  assert_number_of_tabs_open(16);
+    
+  switch_tab(2);
+  assert_selected_and_displayed(msgHdrsInFolder[1]);
+
+  // ... and store the tab titles, to ensure they match with the menu items.
+  let tabTitles = []
+  for (let idx = 0; idx < 16; idx++)
+    tabTitles.unshift(mc.tabmail.tabInfo[idx].title);
 
-    assert_number_of_tabs_open(3);
-    assert_selected_and_displayed(mc, msgHdrsInFolder[4]);
+  // Start the test by closing all tabs except the first two tabs...
+  for (let idx = 0; idx < 14; idx++)
+    mc.tabmail.closeTab(2);
+    
+  assert_number_of_tabs_open(2);
+    
+  // ...then open the context menu.
+  let menu = _synthesizeRecentlyClosedMenu();
 
-    mc.tabmail.undoCloseTab();
-    assert_number_of_tabs_open(4);
-    assert_selected_and_displayed(mc, msgHdrsInFolder[3]);
+  // Check if the context menu was populated correctly...
+  assert_true(menu.itemCount == 12, "Failed to populate context menu");
+  for (let idx=0; idx < 10; idx++)
+    assert_true(tabTitles[idx] == menu.getItemAtIndex(idx).label, 
+        "Tab Title does not match Menu item");
+    
+  // Restore the most recently closed tab
+  EventUtils.synthesizeMouse(menu.getItemAtIndex(0),5, 5, {},mc.window);
+  _teardownRecentlyClosedMenu();
+  
+  wait_for_message_display_completion(mc);
+  assert_number_of_tabs_open(3);
+  assert_selected_and_displayed(msgHdrsInFolder[14]);  
 
-    // msgHdrsInFolder[2] won't be restorend it was closed with disabled undo.
+  // The context menu should now contain one item less.
+  _synthesizeRecentlyClosedMenu();
+      
 
-    mc.tabmail.undoCloseTab();
-    assert_number_of_tabs_open(5);
-    assert_selected_and_displayed(mc, msgHdrsInFolder[1]);
+  assert_true(menu.itemCount == 11, "Failed to populate context menu");
+  for (let idx=0; idx < 9; idx++)
+    assert_true(tabTitles[idx+1] == menu.getItemAtIndex(idx).label, 
+        "Tab Title does not match Menu item");
+        
+  // Now we restore an "random" tab.  
+  EventUtils.synthesizeMouse(menu.getItemAtIndex(5),5, 5, {},mc.window);
+  _teardownRecentlyClosedMenu();
+   
+  wait_for_message_display_completion(mc);
+  assert_number_of_tabs_open(4);    
+  assert_selected_and_displayed(msgHdrsInFolder[8]);
+        
+  // finally restore all tabs 
+  _synthesizeRecentlyClosedMenu();
+  
+  assert_true(menu.itemCount == 10, 
+      "Failed to populate context menu");
+  assert_true(tabTitles[1] == menu.getItemAtIndex(0).label,
+      "Tab Title does not match Menu item");
+  assert_true(tabTitles[7] == menu.getItemAtIndex(5).label,
+      "Tab Title does not match Menu item");
+    
+  EventUtils.synthesizeMouse(menu.getItemAtIndex(menu.itemCount-1),5, 5, {},mc.window);
+  _teardownRecentlyClosedMenu();
+  
+  wait_for_message_display_completion(mc);
+    
+  // out of the 16 tab, we closed all except two. As the history can store 
+  // only 10 items we have to endup with exactly 10 + 2 tabs.
+  assert_number_of_tabs_open(12);    
+}
 
+function teardownTest(test)
+{
+  
+  switch(test)
+  {    
+    case test_tab_reorder_detach :
+    case test_tab_reorder_window :
+      // Some test cases open new windows, thus we need to ensure all 
+      // opened windows get closed.
+
+      let en = Cc["@mozilla.org/appshell/window-mediator;1"]
+                 .getService(Ci.nsIWindowMediator)
+                 .getEnumerator("mail:3pane");
+       
+       while(en.hasMoreElements()) {
+        
+         var win = en.getNext();
+         
+         if(win != mc.window)
+           close_window(new mozmill.controller.MozMillController(win));
+       }
+       
+       // fall through!
+
+    case test_tab_reorder_tabbar :
+        
+    case test_tab_recentlyClosed :
+    case test_tab_undo :
+    
+      // clean up the tabbbar 
+      mc.tabmail.closeOtherTabs(0);
+      assert_number_of_tabs_open(1);
   }
-  finally  {
-    // finally close the tabs opened.
-    mc.tabmail.closeOtherTabs(0);
-    assert_number_of_tabs_open(1);
-  }
+  
 }
 
 /*
  * A set of private helper functions for drag'n'drop
  */
 
 /**
  * Starts a drag new session.