Bug 1459994 - Fix broken tab bindings so "the proper way" of switching tabs works. r=mkmelin,philipp
authorGeoff Lankow <geoff@darktrojan.net>
Tue, 25 Sep 2018 12:59:43 +1200
changeset 33249 2d45d62f063c8ca880ab903cb4b44879bcdd0167
parent 33248 a314b2757253f96e4b523cfc83d9bb6e1e5b1da4
child 33250 d9356caafc10dcb3870ad63f23fda19bc9d8d5ad
push id387
push userclokep@gmail.com
push dateMon, 10 Dec 2018 21:30:47 +0000
reviewersmkmelin, philipp
bugs1459994
Bug 1459994 - Fix broken tab bindings so "the proper way" of switching tabs works. r=mkmelin,philipp
calendar/lightning/content/messenger-overlay-sidebar.js
calendar/test/mozmill/cal-recurrence/testWeeklyWithExceptionRecurrence.js
calendar/test/mozmill/eventDialog/testEventDialog.js
calendar/test/mozmill/recurrenceRotated/testWeeklyWithExceptionRecurrence.js
calendar/test/mozmill/shared-modules/test-calendar-utils.js
calendar/test/mozmill/testBasicFunctionality.js
calendar/test/mozmill/testTimezones.js
calendar/test/mozmill/testTodayPane.js
calendar/test/mozmill/views/testDayView.js
calendar/test/mozmill/views/testMonthView.js
calendar/test/mozmill/views/testMultiweekView.js
calendar/test/mozmill/views/testTaskView.js
calendar/test/mozmill/views/testWeekView.js
mail/base/content/glodaFacetTab.js
mail/base/content/messenger.xul
mail/base/content/specialTabs.js
mail/base/content/tabmail.xml
mail/components/preferences/preferencesTab.js
mail/test/mozmill/shared-modules/test-dom-helpers.js
--- a/calendar/lightning/content/messenger-overlay-sidebar.js
+++ b/calendar/lightning/content/messenger-overlay-sidebar.js
@@ -191,16 +191,17 @@ var calendarItemTabType = {
         moveEventToolbox(clone);
         clone.setAttribute("id", "calendarItemTab" + this.idNumber);
 
         if (aTab.mode.type == "calendarTask") {
             // For task tabs, css class hides event-specific toolbar buttons.
             clone.setAttribute("class", "calendar-task-dialog-tab");
         }
 
+        aTab.panel.setAttribute("id", "calendarItemTabWrapper" + this.idNumber);
         aTab.panel.appendChild(clone);
 
         // Set up the iframe and store the iframe's id.  The iframe's
         // src is set in onLoadLightningItemPanel() that is called below.
         aTab.iframe = aTab.panel.querySelector("iframe");
         let iframeId = "calendarItemTabIframe" + this.idNumber;
         aTab.iframe.setAttribute("id", iframeId);
         gItemTabIds.push(iframeId);
--- a/calendar/test/mozmill/cal-recurrence/testWeeklyWithExceptionRecurrence.js
+++ b/calendar/test/mozmill/cal-recurrence/testWeeklyWithExceptionRecurrence.js
@@ -103,17 +103,17 @@ function testWeeklyWithExceptionRecurren
     switchToView(controller, "day");
     let path = getEventBoxPath("day", EVENT_BOX, null, 1, HOUR) + EVENTPATH;
 
     goToDate(controller, 2009, 1, 5);
     controller.waitForElementNotPresent(lookup(path));
 
     viewForward(controller, 1);
     let tuesPath = `
-        /id("messengerWindow")/id("tabmail-container")/id("tabmail")/
+        /id("messengerWindow")/id("tabmail-container")/id("tabmail")/id("tabmail-tabbox")/
         id("tabpanelcontainer")/id("calendarTabPanel")/id("calendarContent")/
         id("calendarDisplayDeck")/id("calendar-view-box")/id("view-deck")/
         id("day-view")/anon({"anonid":"mainbox"})/anon({"anonid":"scrollbox"})/
         anon({"anonid":"daybox"})/[0]/anon({"anonid":"boxstack"})/
         anon({"anonid":"topbox"})/{"flex":"1"}/{"flex":"1"}/[eventIndex]
     `;
 
     // assert exactly two
@@ -146,17 +146,17 @@ function testWeeklyWithExceptionRecurren
     viewForward(controller, 1);
     controller.waitForElementNotPresent(lookup(path));
 
     // week view
     switchToView(controller, "week");
     goToDate(controller, 2009, 1, 5);
 
     tuesPath = `
-        /id("messengerWindow")/id("tabmail-container")/id("tabmail")/
+        /id("messengerWindow")/id("tabmail-container")/id("tabmail")/id("tabmail-tabbox")/
         id("tabpanelcontainer")/id("calendarTabPanel")/id("calendarContent")/
         id("calendarDisplayDeck")/id("calendar-view-box")/id("view-deck")/
         id("week-view")/anon({"anonid":"mainbox"})/anon({"anonid":"scrollbox"})/
         anon({"anonid":"daybox"})/[dayIndex]/anon({"anonid":"boxstack"})/
         anon({"anonid":"topbox"})/{"flex":"1"}/{"flex":"1"}/[eventIndex]
     `;
 
     // assert exactly two
--- a/calendar/test/mozmill/eventDialog/testEventDialog.js
+++ b/calendar/test/mozmill/eventDialog/testEventDialog.js
@@ -41,17 +41,17 @@ function setupModule(module) {
 
     createCalendar(controller, CALENDARNAME);
 }
 
 function testEventDialog() {
     let dateFormatter = cal.getDateFormatter();
     // paths
     let monthView = `
-        /id("messengerWindow")/id("tabmail-container")/id("tabmail")/
+        /id("messengerWindow")/id("tabmail-container")/id("tabmail")/id("tabmail-tabbox")/
         id("tabpanelcontainer")/id("calendarTabPanel")/id("calendarContent")/
         id("calendarDisplayDeck")/id("calendar-view-box")/id("view-deck")/
         id("month-view")
     `;
     let eventDialog = `
         /id("calendar-event-dialog-inner")/id("event-grid")/id("event-grid-rows")/
     `;
 
--- a/calendar/test/mozmill/recurrenceRotated/testWeeklyWithExceptionRecurrence.js
+++ b/calendar/test/mozmill/recurrenceRotated/testWeeklyWithExceptionRecurrence.js
@@ -107,17 +107,17 @@ function testWeeklyWithExceptionRecurren
     switchToView(controller, "day");
     let path = getEventBoxPath("day", EVENT_BOX, null, 1, HOUR) + EVENTPATH;
 
     goToDate(controller, 2009, 1, 5);
     controller.waitForElementNotPresent(lookup(path));
 
     viewForward(controller, 1);
     let tuesPath = `
-        /id("messengerWindow")/id("tabmail-container")/id("tabmail")/
+        /id("messengerWindow")/id("tabmail-container")/id("tabmail")/id("tabmail-tabbox")/
         id("tabpanelcontainer")/id("calendarTabPanel")/id("calendarContent")/
         id("calendarDisplayDeck")/id("calendar-view-box")/id("view-deck")/
         id("day-view")/anon({"anonid":"mainbox"})/anon({"anonid":"scrollbox"})/
         anon({"anonid":"daybox"})/[0]/anon({"anonid":"boxstack"})/
         anon({"anonid":"topbox"})/{"flex":"1"}/{"flex":"1"}/[eventIndex]
     `;
 
     // assert exactly two
@@ -150,17 +150,17 @@ function testWeeklyWithExceptionRecurren
     viewForward(controller, 1);
     controller.waitForElementNotPresent(lookup(path));
 
     // week view
     switchToView(controller, "week");
     goToDate(controller, 2009, 1, 5);
 
     tuesPath = `
-        /id("messengerWindow")/id("tabmail-container")/id("tabmail")/
+        /id("messengerWindow")/id("tabmail-container")/id("tabmail")/id("tabmail-tabbox")/
         id("tabpanelcontainer")/id("calendarTabPanel")/id("calendarContent")/
         id("calendarDisplayDeck")/id("calendar-view-box")/id("view-deck")/
         id("week-view")/anon({"anonid":"mainbox"})/anon({"anonid":"scrollbox"})/
         anon({"anonid":"daybox"})/[dayIndex]/anon({"anonid":"boxstack"})/
         anon({"anonid":"topbox"})/{"flex":"1"}/{"flex":"1"}/[eventIndex]
     `;
 
     // assert exactly two
--- a/calendar/test/mozmill/shared-modules/test-calendar-utils.js
+++ b/calendar/test/mozmill/shared-modules/test-calendar-utils.js
@@ -211,17 +211,17 @@ function switchToView(controller, view) 
  * @param year          Four-digit year
  * @param month         1-based index of a month
  * @param day           1-based index of a day
  */
 function goToDate(controller, year, month, day) {
     let { eid, lookup } = helpersForController(controller);
 
     let miniMonth = `
-        /id("messengerWindow")/id("tabmail-container")/id("tabmail")/
+        /id("messengerWindow")/id("tabmail-container")/id("tabmail")/id("tabmail-tabbox")/
         id("tabpanelcontainer")/id("calendarTabPanel")/id("calendarContent")/
         id("ltnSidebar")/id("minimonth-pane")/{"align":"center"}/
         id("calMinimonthBox")/id("calMinimonth")
     `;
 
     let activeYear = lookup(`
         ${miniMonth}/anon({"anonid":"minimonth-header"})/
         anon({"anonid":"yearcell"})
@@ -346,17 +346,17 @@ function invokeEventDialog(controller, c
  * @param option        CANVAS_BOX or ALLDAY for creating event, EVENT_BOX for existing event
  * @param row           only used in multiweek and month view, 1-based index of a row
  * @param column        1-based index of a column
  * @param hour          index of hour box
  * @returns             path string
  */
 function getEventBoxPath(controller, view, option, row, column, hour) {
     let viewDeck = `
-        /id("messengerWindow")/id("tabmail-container")/id("tabmail")/
+        /id("messengerWindow")/id("tabmail-container")/id("tabmail")/id("tabmail-tabbox")/
         id("tabpanelcontainer")/id("calendarTabPanel")/id("calendarContent")/
         id("calendarDisplayDeck")/id("calendar-view-box")/id("view-deck")
     `;
 
     let path = `${viewDeck}/id("${view}-view")`;
 
 
     if ((view == "day" || view == "week") && option == ALLDAY) {
@@ -458,17 +458,17 @@ function createCalendar(controller, name
     let manager = win.cal.getCalendarManager();
 
     let url = Services.io.newURI("moz-storage-calendar://");
     let calendar = manager.createCalendar("storage", url);
     calendar.name = name;
     manager.registerCalendar(calendar);
 
     let calendarTree = lookup(`
-        /id("messengerWindow")/id("tabmail-container")/id("tabmail")/
+        /id("messengerWindow")/id("tabmail-container")/id("tabmail")/id("tabmail-tabbox")/
         id("tabpanelcontainer")/id("calendarTabPanel")/id("calendarContent")/
         id("ltnSidebar")/id("calendar-panel")/id("calendar-list-pane")/
         id("calendar-listtree-pane")/id("calendar-list-tree-widget")
     `).getNode();
 
     for (let i = 0; i < calendarTree.mCalendarList.length; i++) {
         if (calendarTree.mCalendarList[i].id == calendar.id) {
             calendarTree.tree.view.selection.select(i);
--- a/calendar/test/mozmill/testBasicFunctionality.js
+++ b/calendar/test/mozmill/testBasicFunctionality.js
@@ -25,17 +25,17 @@ function setupModule(module) {
     } = collector.getModule("calendar-utils"));
     collector.getModule("calendar-utils").setupModule();
     Object.assign(module, helpersForController(controller));
 }
 
 function testSmokeTest() {
     let dateFormatter = cal.getDateFormatter();
     let path = `
-        /id("messengerWindow")/id("tabmail-container")/id("tabmail")/
+        /id("messengerWindow")/id("tabmail-container")/id("tabmail")/id("tabmail-tabbox")/
         id("tabpanelcontainer")/id("calendarTabPanel")/id("calendarContent")
     `;
 
     // open calendar view
     controller.click(eid("calendar-tab-button"));
 
     // check for minimonth
     controller.waitForElement(eid("calMinimonth"));
--- a/calendar/test/mozmill/testTimezones.js
+++ b/calendar/test/mozmill/testTimezones.js
@@ -267,17 +267,17 @@ function verify(controller, dates, timez
         for (let idx = 0; idx < dates.length; idx++) {
             yield [dates[idx][0], dates[idx][1], dates[idx][2], times[idx]];
         }
     }
 
     let { lookup } = helpersForController(controller);
 
     let dayView = `
-        /id("messengerWindow")/id("tabmail-container")/id("tabmail")/
+        /id("messengerWindow")/id("tabmail-container")/id("tabmail")/id("tabmail-tabbox")/
         id("tabpanelcontainer")/id("calendarTabPanel")/id("calendarContent")/
         id("calendarDisplayDeck")/id("calendar-view-box")/id("view-deck")/
         id("day-view")
     `;
     let dayStack = `
         ${dayView}/anon({"anonid":"mainbox"})/anon({"anonid":"scrollbox"})/
         anon({"anonid":"daybox"})/[0]/anon({"anonid":"boxstack"})/
         anon({"anonid":"topbox"})/{"flex":"1"}
--- a/calendar/test/mozmill/testTodayPane.js
+++ b/calendar/test/mozmill/testTodayPane.js
@@ -25,17 +25,17 @@ function setupModule(module) {
 
     createCalendar(controller, CALENDARNAME);
 }
 
 function testTodayPane() {
     // paths
     let panels = `
         /id("messengerWindow")/id("tabmail-container")/
-        id("tabmail")/id("tabpanelcontainer")
+        id("tabmail")/id("tabmail-tabbox")/id("tabpanelcontainer")
     `;
     let miniMonth = `
         ${panels}/id("calendarTabPanel")/id("calendarContent")/id("ltnSidebar")/
         id("minimonth-pane")
     `;
     let dayView = `
         ${panels}/id("calendarTabPanel")/id("calendarContent")/
         id("calendarDisplayDeck")/id("calendar-view-box")/
--- a/calendar/test/mozmill/views/testDayView.js
+++ b/calendar/test/mozmill/views/testDayView.js
@@ -34,17 +34,17 @@ function setupModule(module) {
 
     createCalendar(controller, CALENDARNAME);
 }
 
 function testDayView() {
     let dateFormatter = cal.getDateFormatter();
     // paths
     let dayView = `
-        /id("messengerWindow")/id("tabmail-container")/id("tabmail")/
+        /id("messengerWindow")/id("tabmail-container")/id("tabmail")/id("tabmail-tabbox")/
         id("tabpanelcontainer")/id("calendarTabPanel")/id("calendarContent")/
         id("calendarDisplayDeck")/id("calendar-view-box")/id("view-deck")/
         id("day-view")
     `;
 
     // open day view
     controller.click(eid("calendar-tab-button"));
     controller.waitThenClick(eid("calendar-day-view-button"));
--- a/calendar/test/mozmill/views/testMonthView.js
+++ b/calendar/test/mozmill/views/testMonthView.js
@@ -34,17 +34,17 @@ function setupModule(module) {
 
     createCalendar(controller, CALENDARNAME);
 }
 
 function testMonthView() {
     let dateFormatter = cal.getDateFormatter();
     // paths
     let monthView = `
-        /id("messengerWindow")/id("tabmail-container")/id("tabmail")/
+        /id("messengerWindow")/id("tabmail-container")/id("tabmail")/id("tabmail-tabbox")/
         id("tabpanelcontainer")/id("calendarTabPanel")/id("calendarContent")/
         id("calendarDisplayDeck")/id("calendar-view-box")/id("view-deck")/
         id("month-view")
     `;
 
     controller.click(eid("calendar-tab-button"));
     controller.waitThenClick(eid("calendar-month-view-button"));
 
--- a/calendar/test/mozmill/views/testMultiweekView.js
+++ b/calendar/test/mozmill/views/testMultiweekView.js
@@ -34,17 +34,17 @@ function setupModule(module) {
 
     createCalendar(controller, CALENDARNAME);
 }
 
 function testMultiWeekView() {
     let dateFormatter = cal.getDateFormatter();
     // paths
     let multiWeekView = `
-        /id("messengerWindow")/id("tabmail-container")/id("tabmail")/
+        /id("messengerWindow")/id("tabmail-container")/id("tabmail")/id("tabmail-tabbox")/
         id("tabpanelcontainer")/id("calendarTabPanel")/id("calendarContent")/
         id("calendarDisplayDeck")/id("calendar-view-box")/id("view-deck")/
         id("multiweek-view")/
     `;
 
     controller.click(eid("calendar-tab-button"));
     controller.waitThenClick(eid("calendar-multiweek-view-button"));
 
--- a/calendar/test/mozmill/views/testTaskView.js
+++ b/calendar/test/mozmill/views/testTaskView.js
@@ -29,17 +29,17 @@ function setupModule(module) {
     createCalendar(controller, CALENDARNAME);
 }
 
 // mozmill doesn't support trees yet, therefore completed checkbox and line-through style are not
 // checked
 function testTaskView() {
     // paths
     let taskView = `
-        /id("messengerWindow")/id("tabmail-container")/id("tabmail")/
+        /id("messengerWindow")/id("tabmail-container")/id("tabmail")/id("tabmail-tabbox")/
         id("tabpanelcontainer")/id("calendarTabPanel")/id("calendarContent")/
         id("calendarDisplayDeck")/id("calendar-task-box")/
     `;
     let treeChildren = `
         ${taskView}/[1]/id("calendar-task-tree")/
         anon({"anonid":"calendar-task-tree"})/{"tooltip":"taskTreeTooltip"}
     `;
     let taskTree = taskView + '[1]/id("calendar-task-tree")';
@@ -47,17 +47,17 @@ function testTaskView() {
     let toolTipGrid = toolTip + '/{"class":"tooltipBox"}/{"class":"tooltipHeaderGrid"}/';
 
     // open task view
     controller.click(eid("task-tab-button"));
     sleep();
 
     // make sure that testing calendar is selected
     let calendarTree = lookup(`
-        /id("messengerWindow")/id("tabmail-container")/id("tabmail")/
+        /id("messengerWindow")/id("tabmail-container")/id("tabmail")/id("tabmail-tabbox")/
         id("tabpanelcontainer")/id("calendarTabPanel")/id("calendarContent")/
         id("ltnSidebar")/id("calendar-panel")/id("calendar-list-pane")/
         id("calendar-listtree-pane")/id("calendar-list-tree-widget")
     `).getNode();
 
     for (let i = 0; i < calendarTree.mCalendarList.length; i++) {
         if (calendarTree.mCalendarList[i].name == CALENDARNAME) {
             calendarTree.tree.view.selection.select(i);
--- a/calendar/test/mozmill/views/testWeekView.js
+++ b/calendar/test/mozmill/views/testWeekView.js
@@ -34,17 +34,17 @@ function setupModule(module) {
 
     createCalendar(controller, CALENDARNAME);
 }
 
 function testWeekView() {
     let dateFormatter = cal.getDateFormatter();
     // paths
     let weekView = `
-        /id("messengerWindow")/id("tabmail-container")/id("tabmail")/
+        /id("messengerWindow")/id("tabmail-container")/id("tabmail")/id("tabmail-tabbox")/
         id("tabpanelcontainer")/id("calendarTabPanel")/id("calendarContent")/
         id("calendarDisplayDeck")/id("calendar-view-box")/id("view-deck")/
         id("week-view")/
     `;
 
     controller.click(eid("calendar-tab-button"));
     controller.waitThenClick(eid("calendar-week-view-button"));
 
--- a/mail/base/content/glodaFacetTab.js
+++ b/mail/base/content/glodaFacetTab.js
@@ -6,16 +6,17 @@ ChromeUtils.import("resource:///modules/
 
 ChromeUtils.import("resource:///modules/gloda/facet.js");
 // needed by search.xml to use us
 ChromeUtils.import("resource:///modules/gloda/msg_search.js");
 
 var glodaFacetTabType = {
   name: "glodaFacet",
   perTabPanel: "vbox",
+  lastTabId: 0,
   strings:
     new StringBundle("chrome://messenger/locale/glodaFacetView.properties"),
   modes: {
     glodaFacet: {
       // this is what get exposed on the tab for icon purposes
       type: "glodaSearch"
     }
   },
@@ -23,16 +24,17 @@ var glodaFacetTabType = {
     // we have no browser until our XUL document loads
     aTab.browser = null;
 
     // First clone the page and set up the basics.
     let clone = document.getElementById("glodaTab")
                         .firstChild
                         .cloneNode(true);
 
+    aTab.panel.setAttribute("id", "glodaTab" + this.lastTabId);
     aTab.panel.appendChild(clone);
     aTab.iframe = aTab.panel.querySelector("iframe");
 
     // Wire up the search input icon click event
     let searchInput = aTab.panel.querySelector(".remote-gloda-search");
     searchInput.focus();
 
     if ("query" in aArgs) {
@@ -69,16 +71,18 @@ var glodaFacetTabType = {
       aTab.browser = aTab.iframe.contentDocument.getElementById("browser");
       aTab.browser.setAttribute("src",
         "chrome://messenger/content/glodaFacetView.xhtml");
     }
 
     aTab.iframe.contentWindow.addEventListener("load", xulLoadHandler, {capture: false, once: true});
     aTab.iframe.setAttribute("src",
       "chrome://messenger/content/glodaFacetViewWrapper.xul");
+
+    this.lastTabId++;
   },
   closeTab: function glodaFacetTabType_closeTab(aTab) {
   },
   saveTabState: function glodaFacetTabType_saveTabState(aTab) {
     // nothing to do; we are not multiplexed
   },
   showTab: function glodaFacetTabType_showTab(aTab) {
     // nothing to do; we are not multiplexed
--- a/mail/base/content/messenger.xul
+++ b/mail/base/content/messenger.xul
@@ -349,17 +349,17 @@
             onclick="document.getElementById('tabmail').onTabClick(event);"
             class="tabmail-tabs"
             tooltip="tabmail-tabs-tooltip"
             alltabsbutton="alltabs-button"
             collapsetoolbar="tabs-toolbar"
             tabtoolbar="tabbar-toolbar">
             <tab selected="true" validate="never" type="folder"
                  maxwidth="250" width="0" minwidth="100" flex="100"
-                 class="tabmail-tab" crop="end"/>
+                 class="tabmail-tab" crop="end" linkedpanel="mailContent"/>
       </tabs>
 
       <!-- Use of this element for extensions is deprecated! Current
            extensions should add to #mail-toolbox and add a toolbar item to
            #tabbar-toolbar below. -->
       <hbox id="tabmail-buttons"/>
 
       <toolbar id="tabbar-toolbar" toolboxid="mail-toolbox"
@@ -395,16 +395,17 @@
        Horizontal space shouldn't be wasted if it isn't absolutely critical.
        A mechanism for adding sidebar panes will be added in bug 476154. -->
   <hbox id="tabmail-container" flex="1">
     <!-- Beware!  Do NOT use overlays to append nodes directly to tabmail (children
          of tabmail is OK though).  This will break Ctrl-tab switching because
          the XBL binding will choke when it finds a child of tabmail that is
          not a tabpanels node. -->
     <tabmail id="tabmail" flex="1" panelcontainer="tabpanelcontainer" tabcontainer="tabmail-tabs">
+     <tabbox id="tabmail-tabbox" flex="1" eventnode="document" tabcontainer="tabmail-tabs">
       <tabpanels id="tabpanelcontainer" flex="1" class="plain" selectedIndex="0">
 
         <!-- mailContent is the container used for the "wide" layout. Normally,
              all it contains is the "messengerBox" box.  However, in "wide" mode
              the message pane and its splitter transplant themselves into the box
              (respectively, messagepanebox and threadpane-splitter).  This gives us
              the folder pane next to the thread view, with the message pane/reader
              beneath both of them. -->
@@ -655,16 +656,17 @@
                 </hbox>
                <notificationbox id="msg-footer-notification-box" notificationside="bottom"/>
               </box>
             </vbox>
           </box>
         </box> <!-- end of mailContent -->
 #include ../../components/im/content/chat-messenger.inc
       </tabpanels>
+     </tabbox>
     </tabmail>
     <vbox id="contentTab" collapsed="true">
       <vbox flex="1" class="contentTabInstance">
         <toolbox id="dummycontenttoolbox" class="contentTabToolbox">
           <toolbar id="dummycontenttoolbar" class="contentTabToolbar">
           </toolbar>
         </toolbox>
         <notificationbox flex="1" notificationside="top">
--- a/mail/base/content/specialTabs.js
+++ b/mail/base/content/specialTabs.js
@@ -696,16 +696,17 @@ var specialTabs = {
 
       clone.setAttribute("id", "contentTab" + this.lastBrowserId);
       clone.setAttribute("collapsed", false);
 
       let toolbox = clone.firstChild;
       toolbox.setAttribute("id", "contentTabToolbox" + this.lastBrowserId);
       toolbox.firstChild.setAttribute("id", "contentTabToolbar" + this.lastBrowserId);
 
+      aTab.panel.setAttribute("id", "contentTabWrapper" + this.lastBrowserId);
       aTab.panel.appendChild(clone);
       aTab.root = clone;
 
       // Start setting up the browser.
       aTab.browser = aTab.panel.querySelector("browser");
       aTab.toolbar = aTab.panel.querySelector(".contentTabToolbar");
 
       ExtensionParent.apiManager.emit("extension-browser-inserted", aTab.browser);
@@ -1160,16 +1161,17 @@ var specialTabs = {
 
       clone.setAttribute("id", "chromeTab" + this.lastBrowserId);
       clone.setAttribute("collapsed", false);
 
       let toolbox = clone.firstChild;
       toolbox.setAttribute("id", "chromeTabToolbox" + this.lastBrowserId);
       toolbox.firstChild.setAttribute("id", "chromeTabToolbar" + this.lastBrowserId);
 
+      aTab.panel.setAttribute("id", "chromeTabWrapper" + this.lastBrowserId);
       aTab.panel.appendChild(clone);
 
       // Start setting up the browser.
       aTab.browser = aTab.panel.querySelector("browser");
 
       // As we're opening this tab, showTab may not get called, so set
       // the type according to if we're opening in background or not.
       let background = ("background" in aArgs) && aArgs.background;
--- a/mail/base/content/tabmail.xml
+++ b/mail/base/content/tabmail.xml
@@ -258,32 +258,28 @@
     -  logic.  We support the standard supportsCommand/isCommandEnabled/
     -  doCommand functions but with a twist to indicate when other tab monitors
     -  and the actual tab itself should get a chance to process: supportsCommand
     -  and isCommandEnabled should return null when they are not handling the
     -  case.  doCommand should return true if it handled the case, null
     -  otherwise.
     -->
   <binding id="tabmail">
-    <content>
-      <xul:tabbox anonid="tabbox" class="tabmail-tabbox" flex="1" eventnode="document" tabcontainer="tabmail-tabs">
-        <!-- Remember, user of this binding, you need to provide tabpanels!  -->
-        <children includes="tabpanels"/>
-      </xul:tabbox>
-    </content>
-
     <implementation implements="nsIController, nsIWebProgressListener, nsIWebProgressListener2">
       <constructor>
         window.controllers.insertControllerAt(0, this);
         this._restoringTabState = null;
         ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
       </constructor>
       <destructor>
         window.controllers.removeController(this);
       </destructor>
+      <field name="tabbox" readonly="true">
+        this.getElementsByTagName("tabbox").item(0)
+      </field>
       <field name="currentTabInfo">
         null
       </field>
       <!-- Temporary field that only has a non-null value during a call to
            openTab, and whose value is the currentTabInfo of the tab that was
            open when we received the call to openTab. -->
       <field name="_mostRecentTabInfo">
         null
@@ -534,17 +530,16 @@
           t.className = "tabmail-tab";
           this.tabContainer.appendChild(t);
           if (this.tabContainer.mCollapseToolbar.collapsed) {
             this.tabContainer.mCollapseToolbar.collapsed = false;
             this.tabContainer._updateCloseButtons();
           }
 
           let oldTab = this._mostRecentTabInfo = this.currentTabInfo;
-          let oldPanel = this.panelContainer.selectedPanel;
 
           let disregardOpener = ("disregardOpener" in aArgs)
                                 && aArgs.disregardOpener;
 
           // If we're not disregarding the opening, hold a reference to opener
           // so that if the new tab is closed without switching, we can switch
           // back to the opener tab.
           if (disregardOpener)
@@ -564,33 +559,47 @@
 
           // make sure we are on the right panel
           if (tab.mode.tabType.perTabPanel) {
             // should we create the element for them, or will they do it?
             if (typeof(tab.mode.tabType.perTabPanel) == "string") {
               tab.panel = document.createElementNS(
                 "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
                 tab.mode.tabType.perTabPanel);
-            }
-            else {
+            } else {
               tab.panel = tab.mode.tabType.perTabPanel(tab);
             }
             this.panelContainer.appendChild(tab.panel);
-            if (!background)
+            if (!background) {
               this.panelContainer.selectedPanel = tab.panel;
+            }
+          } else {
+            if (!background) {
+              this.panelContainer.selectedPanel = tab.mode.tabType.panel;
+            }
+            t.linkedPanel = tab.mode.tabType.panelId;
           }
-          else if (!background)
-            this.panelContainer.selectedPanel = tab.mode.tabType.panel;
 
           // Make sure the new panel is marked selected.
-          oldPanel.removeAttribute("selected");
+          let oldPanel = [...this.panelContainer.children].find(p => p.hasAttribute("selected"));
+          if (oldPanel) {
+            oldPanel.removeAttribute("selected");
+          }
           this.panelContainer.selectedPanel.setAttribute("selected", "true");
 
           let tabOpenFunc = tab.mode.openTab || tab.mode.tabType.openTab;
           tabOpenFunc.apply(tab.mode.tabType, [tab, aArgs]);
+          if (!t.linkedPanel) {
+            if (!tab.panel.id) {
+              // No id set. Create our own.
+              tab.panel.id = "unnamedTab" + Math.random().toString().substring(2);
+              console.warn(`Tab mode ${aTabModeName} should set an id on the first argument of openTab.`);
+            }
+            t.linkedPanel = tab.panel.id;
+          }
 
           let restoreState = this._restoringTabState;
           for (let tabMonitor of this.tabMonitors) {
             if (("onTabRestored" in tabMonitor) && restoreState &&
                 (tabMonitor.monitorName in restoreState.ext))
               tabMonitor.onTabRestored(tab,
                                        restoreState.ext[tabMonitor.monitorName],
                                        false);
@@ -1225,25 +1234,24 @@
         <body>
           <![CDATA[
             if (this.currentTabInfo != this.tabInfo[this.tabContainer.selectedIndex])
             {
               if (this.currentTabInfo)
                 this.saveCurrentTabState();
 
               let oldTab = this.currentTabInfo;
-              let oldPanel = this.panelContainer.selectedPanel;
+              let oldPanel = [...this.panelContainer.children].find(p => p.hasAttribute("selected"));
               let tab = this.currentTabInfo =
                 this.tabInfo[this.tabContainer.selectedIndex];
 
-              this.panelContainer.selectedPanel = tab.panel ||
-                                                  tab.mode.tabType.panel;
-
               // Update the selected attribute on the current and old tab panel.
-              oldPanel.removeAttribute("selected");
+              if (oldPanel) {
+                oldPanel.removeAttribute("selected");
+              }
               this.panelContainer.selectedPanel.setAttribute("selected", "true");
 
               let showTabFunc = tab.mode.showTab || tab.mode.tabType.showTab;
               showTabFunc.call(tab.mode.tabType, tab);
 
               for (let tabMonitor of this.tabMonitors) {
                 tabMonitor.onTabSwitched(tab, oldTab);
               }
@@ -2008,17 +2016,17 @@
         ]]>
       </destructor>
 
       <field name="tabmail" readonly="true">
         document.getElementById("tabmail");
       </field>
 
       <field name="tabbox" readonly="true">
-        document.getAnonymousElementByAttribute(this, "anonid", "tabbox");
+        this.tabmail.tabbox;
       </field>
 
       <field name="arrowScrollboxWidth">0</field>
 
       <field name="arrowScrollbox">
         document.getAnonymousElementByAttribute(this, "anonid", "arrowscrollbox");
       </field>
 
@@ -2079,16 +2087,42 @@
             if (this.childNodes.length == 1)
               this.mCollapseToolbar.collapsed = val;
             this._mAutoHide = val;
           }
           return val;
         ]]></setter>
       </property>
 
+      <property name="selectedIndex">
+        <getter>
+        <![CDATA[
+          return this.__proto__.__proto__.__lookupGetter__("selectedIndex").call(this);
+        ]]>
+        </getter>
+
+        <setter>
+        <![CDATA[
+          let tab = this.getItemAtIndex(val);
+          let alreadySelected = tab && tab.selected;
+
+          this.__proto__.__proto__.__lookupSetter__("selectedIndex").call(this, val);
+
+          if (!alreadySelected) {
+            // Fire an onselect event for the tabs element.
+            var event = document.createEvent("Events");
+            event.initEvent("select", true, true);
+            this.dispatchEvent(event);
+          }
+
+          return val;
+        ]]>
+        </setter>
+      </property>
+
       <method name="_updateCloseButtons">
         <body><![CDATA[
           // modes for tabstrip
           // 0 - activetab  = close button on active tab only
           // 1 - alltabs    = close buttons on all tabs
           // 2 - noclose    = no close buttons at all
           // 3 - closeatend = close button at the end of the tabstrip
           switch (this.mCloseButtons) {
--- a/mail/components/preferences/preferencesTab.js
+++ b/mail/components/preferences/preferencesTab.js
@@ -61,16 +61,17 @@ var preferencesTabType = {
 
     // First clone the page and set up the basics.
     let clone = document.getElementById("preferencesTab").firstChild
                         .cloneNode(true);
 
     clone.setAttribute("id", "preferencesTab" + this.lastBrowserId);
     clone.setAttribute("collapsed", false);
 
+    aTab.panel.setAttribute("id", "preferencesTabWrapper" + this.lastBrowserId);
     aTab.panel.appendChild(clone);
 
     // Start setting up the browser.
     aTab.browser = aTab.panel.querySelector("browser");
 
     aTab.browser.setAttribute("id", "preferencesTabBrowser" + this.lastBrowserId);
 
     aTab.browser.addEventListener("DOMLinkAdded", DOMLinkHandler);
--- a/mail/test/mozmill/shared-modules/test-dom-helpers.js
+++ b/mail/test/mozmill/shared-modules/test-dom-helpers.js
@@ -80,18 +80,21 @@ function assert_element_visible(aElt, aW
  * @param aElem The element to be checked
  */
 function element_visible_recursive(aElem) {
   if (aElem.hidden || aElem.collapsed)
     return false;
   let parent = aElem.parentNode;
   if (parent == null)
     return true;
+
+  // #tabpanelcontainer and its parent #tabmail-tabbox have the same selectedPanel.
+  // Don't ask me why, it's just the way it is.
   if (("selectedPanel" in parent) &&
-      parent.selectedPanel != aElem)
+      parent.selectedPanel != aElem && aElem.id != "tabpanelcontainer")
     return false;
   return element_visible_recursive(parent);
 }
 
 /**
  * Assert that en element's not visible.
  * @param aElt The element, an ID or an elementlibs.Elem
  * @param aWhy The error message in case of failure