Bug 1554630 - convert the tabmail-tabs binding to custom element. rs=bustage-fix
authorMagnus Melin <mkmelin+mozilla@iki.fi>, Alessandro Castellani <alessandro@thunderbird.net>
Sat, 29 Jun 2019 23:01:55 +0300
changeset 35991 99a134f08efeb1e6e54dd45f755af00f2b49669a
parent 35990 5d1b0dbe9f0c97d1f10cda704aa61cc2fd4c6366
child 35992 68fe7b1e877f6229e081e59b683be502eac7b5a8
push id392
push userclokep@gmail.com
push dateMon, 02 Sep 2019 20:17:19 +0000
reviewersbustage-fix
bugs1554630
Bug 1554630 - convert the tabmail-tabs binding to custom element. rs=bustage-fix
mail/base/content/messenger.xul
mail/base/content/tabmail-tabs.js
mail/base/content/tabmail.css
mail/base/content/tabmail.xml
mail/base/jar.mn
mail/components/extensions/test/browser/browser_ext_menus.js
mail/test/mozmill/content-policy/test-compose-mailto.js
mail/test/mozmill/content-policy/test-dns-prefetch.js
mail/test/mozmill/content-policy/test-exposed-in-content-tabs.js
mail/test/mozmill/content-policy/test-general-content-policy.js
mail/test/mozmill/content-policy/test-plugins-policy.js
mail/test/mozmill/content-tabs/test-content-tab.js
mail/test/mozmill/downloads/test-about-downloads.js
mail/test/mozmill/folder-display/test-displaying-messages-in-folder-tabs.js
mail/test/mozmill/folder-display/test-opening-messages-without-a-backing-view.js
mail/test/mozmill/folder-display/test-opening-messages.js
mail/test/mozmill/im/test-chat-tab-restore.js
mail/test/mozmill/newmailaccount/test-newmailaccount.js
mail/test/mozmill/search-window/test-search-window.js
mail/test/mozmill/shared-modules/test-content-tab-helpers.js
mail/test/mozmill/shared-modules/test-folder-display-helpers.js
mail/test/mozmill/tabmail/test-tabmail-dragndrop.js
mail/themes/linux/mail/tabmail.css
mail/themes/windows/mail/tabmail.css
--- a/mail/base/content/messenger.xul
+++ b/mail/base/content/messenger.xul
@@ -152,16 +152,17 @@
 <script src="chrome://messenger/content/quickFilterBar.js"/>
 <script src="chrome://messenger/content/newmailaccount/uriListener.js"/>
 <script src="chrome://messenger/content/chat/chat-conversation-info.js"/>
 <script src="chrome://gloda/content/autocomplete-richlistitem.js"/>
 <script src="chrome://messenger/content/chat/chat-contact.js"/>
 <script src="chrome://messenger/content/chat/chat-group.js"/>
 <script src="chrome://messenger/content/chat/chat-imconv.js"/>
 <script src="chrome://messenger/content/tabbrowser-tab.js"/>
+<script src="chrome://messenger/content/tabmail-tabs.js"/>
 <script src="chrome://messenger/content/chat/toolbarbutton-badge-button.js"/>
 <!-- panelUI.js is for the appmenus. -->
 <script src="chrome://messenger/content/customizableui/panelUI.js"/>
 #ifdef XP_MACOSX
 <script src="chrome://messenger/content/macMessengerMenu.js"/>
 <script src="chrome://global/content/macWindowMenu.js"/>
 #endif
 
@@ -389,30 +390,40 @@
 #endif
   </hbox>
 </vbox>
 
   <!-- navigation-toolbox with main menubar and tabs toolbar -->
 #include mainNavigationToolbox.inc.xul
 
     <toolbar id="tabs-toolbar" class="chromeclass-toolbar">
-      <!-- class tabmail-tabs is unused and only maintained for add-ons. -->
-      <tabs flex="1"
-            id="tabmail-tabs"
+      <tabs is="tabmail-tabs" id="tabmail-tabs"
+            flex="1"
             align="end"
             setfocus="false"
             onclick="document.getElementById('tabmail').onTabClick(event);"
-            class="tabmail-tabs"
             tooltip="tabmail-tabs-tooltip"
             alltabsbutton="alltabs-button"
             collapsetoolbar="tabs-toolbar"
-            tabtoolbar="tabbar-toolbar">
-            <tab is="tabmail-tab" selected="true" validate="never" type="folder"
-                 maxwidth="250" width="0" minwidth="100" flex="100"
-                 class="tabmail-tab" crop="end" linkedpanel="mailContent"/>
+            tabtoolbar="tabbar-toolbar"
+            context="toolbar-context-menu">
+        <hbox class="tab-drop-indicator-box">
+          <image class="tab-drop-indicator" hidden="true"/>
+        </hbox>
+        <arrowscrollbox class="tabmail-arrowscrollbox"
+                        orient="horizontal"
+                        flex="1"
+                        style="min-width: 1px;">
+          <tab is="tabmail-tab" selected="true" validate="never" type="folder"
+               maxwidth="250" width="0" minwidth="100" flex="100"
+               class="tabmail-tab" crop="end" linkedpanel="mailContent"/>
+        </arrowscrollbox>
+        <hbox class="tabs-closebutton-box" align="center" pack="end">
+          <image class="close-icon tabs-closebutton"></image>
+        </hbox>
       </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"/>
 
       <box id="notification-popup-box"
new file mode 100644
--- /dev/null
+++ b/mail/base/content/tabmail-tabs.js
@@ -0,0 +1,738 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+  * License, v. 2.0. If a copy of the MPL was not distributed with this
+  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* global MozElements, MozXULElement */
+
+{
+  const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+  /**
+   * The MozTabs widget holds all the tabs for the main tab UI.
+   * @extends {MozTabs}
+   */
+  class MozTabmailTabs extends customElements.get("tabs") {
+    constructor() {
+      super();
+
+      this.addEventListener("dragstart", (event) => {
+        let draggedTab = this._getDragTargetTab(event);
+
+        if (!draggedTab)
+          return;
+
+        let tab = this.tabmail.selectedTab;
+
+        if (!tab || !tab.canClose)
+          return;
+
+        let dt = event.dataTransfer;
+
+        // If we drag within the same window, we use the tab directly
+        dt.mozSetDataAt("application/x-moz-tabmail-tab", draggedTab, 0);
+
+        // Otherwise we use session restore & JSON to migrate the tab.
+        let uri = this.tabmail.persistTab(tab);
+
+        // In case the tab implements session restore, we use JSON to convert
+        // it into a string.
+        //
+        // If a tab does not support session restore it returns null. We can't
+        // moved such tabs to a new window. However moving them within the same
+        // window works perfectly fine.
+        if (uri)
+          uri = JSON.stringify(uri);
+
+        dt.mozSetDataAt("application/x-moz-tabmail-json", uri, 0);
+
+        dt.mozCursor = "default";
+
+        // Create Drag Image.
+        let panel = document.getElementById("tabpanelcontainer");
+
+        let thumbnail = document.createElementNS("http://www.w3.org/1999/xhtml",
+                                                 "canvas");
+        thumbnail.width = Math.ceil(screen.availWidth / 5.75);
+        thumbnail.height = Math.round(thumbnail.width * 0.5625);
+
+        let snippetWidth = panel.getBoundingClientRect().width * .6;
+        let scale = thumbnail.width / snippetWidth;
+
+        let ctx = thumbnail.getContext("2d");
+
+        ctx.scale(scale, scale);
+
+        ctx.drawWindow(window,
+          panel.screenX - window.mozInnerScreenX,
+          panel.screenY - window.mozInnerScreenY,
+          snippetWidth,
+          snippetWidth * 0.5625,
+          "rgb(255,255,255)");
+
+        dt = event.dataTransfer;
+        dt.setDragImage(thumbnail, 0, 0);
+
+        event.stopPropagation();
+      });
+
+      this.addEventListener("dragover", (event) => {
+        let dt = event.dataTransfer;
+
+        if (dt.mozItemCount == 0)
+          return;
+
+        if (dt.mozGetDataAt("text/toolbarwrapper-id/messengerWindow", 0) !=
+            null) {
+          event.preventDefault();
+          event.stopPropagation();
+
+          // Dispatch event to the toolbar
+          let evt = document.createEvent("DragEvent");
+          evt.initDragEvent("dragover", true, true, window, 0, 0, 0, 0, 0,
+            false, false, false, false, 0, null,
+            event.dataTransfer);
+
+          if (this.mToolbar.firstChild) {
+            this.mToolbar.firstChild.dispatchEvent(evt);
+          } else {
+            this.mToolbar.dispatchEvent(evt);
+          }
+
+          return;
+        }
+
+        // Bug 516247:
+        // in case the user is dragging something else than a tab, and
+        // keeps hovering over a tab, we assume he wants to switch to this tab.
+        if ((dt.mozTypesAt(0)[0] != "application/x-moz-tabmail-tab") &&
+            (dt.mozTypesAt(0)[1] != "application/x-moz-tabmail-json")) {
+          let tab = this._getDragTargetTab(event);
+
+          if (!tab)
+            return;
+
+          event.preventDefault();
+          event.stopPropagation();
+
+          if (!this._dragTime) {
+            this._dragTime = Date.now();
+            return;
+          }
+
+          if (Date.now() <= this._dragTime + this._dragOverDelay)
+            return;
+
+          if (this.tabmail.tabContainer.selectedItem == tab)
+            return;
+
+          this.tabmail.tabContainer.selectedItem = tab;
+
+          return;
+        }
+
+        // As some tabs do not support session restore they can't be
+        // moved to a different or new window. We should not show
+        // a dropmarker in such a case.
+        if (!dt.mozGetDataAt("application/x-moz-tabmail-json", 0)) {
+          let draggedTab = dt.mozGetDataAt("application/x-moz-tabmail-tab", 0);
+
+          if (!draggedTab)
+            return;
+
+          if (this.tabmail.tabContainer.getIndexOfItem(draggedTab) == -1)
+            return;
+        }
+
+        dt.effectAllowed = "copyMove";
+
+        event.preventDefault();
+        event.stopPropagation();
+
+        let ltr = (window.getComputedStyle(this).direction == "ltr");
+        let ind = this._tabDropIndicator;
+
+        // Let's scroll
+        if (this.hasAttribute("overflow")) {
+          let target = event.originalTarget.getAttribute("anonid");
+
+          let pixelsToScroll = 0;
+
+          if (target == "scrollbutton-up")
+            pixelsToScroll = this.arrowScrollbox.scrollIncrement;
+
+          if (target == "scrollbutton-down")
+            pixelsToScroll = this.arrowScrollbox.scrollIncrement * -1;
+
+          if (ltr)
+            pixelsToScroll = pixelsToScroll * -1;
+
+          if (pixelsToScroll) {
+            // Hide Indicator while Scrolling
+            ind.setAttribute("hidden", "true");
+            this.arrowScrollbox.scrollByPixels(pixelsToScroll);
+            return;
+          }
+        }
+
+        let newIndex = this._getDropIndex(event);
+
+        // Fix the DropIndex in case it points to tab that can't be closed.
+        let tabInfo = this.tabmail.tabInfo;
+
+        while ((newIndex < tabInfo.length) && !(tabInfo[newIndex].canClose)) {
+          newIndex++;
+        }
+
+        let scrollRect = this.arrowScrollbox.scrollClientRect;
+        let rect = this.getBoundingClientRect();
+        let minMargin = scrollRect.left - rect.left;
+        let maxMargin = Math.min(minMargin + scrollRect.width, scrollRect.right);
+
+        if (!ltr)
+          [minMargin, maxMargin] = [this.clientWidth - maxMargin, this.clientWidth - minMargin];
+
+        let newMargin;
+        let tabs = this.allTabs;
+
+        if (newIndex == tabs.length) {
+          let tabRect = tabs[newIndex - 1].getBoundingClientRect();
+
+          if (ltr)
+            newMargin = tabRect.right - rect.left;
+          else
+            newMargin = rect.right - tabRect.left;
+        } else {
+          let tabRect = tabs[newIndex].getBoundingClientRect();
+
+          if (ltr)
+            newMargin = tabRect.left - rect.left;
+          else
+            newMargin = rect.right - tabRect.right;
+        }
+
+        ind.setAttribute("hidden", "false");
+
+        newMargin += ind.clientWidth / 2;
+        if (!ltr)
+          newMargin *= -1;
+
+        ind.style.transform = "translate(" + Math.round(newMargin) + "px)";
+        ind.style.marginInlineStart = (-ind.clientWidth) + "px";
+      });
+
+      this.addEventListener("drop", (event) => {
+        let dt = event.dataTransfer;
+
+        if (dt.mozItemCount != 1)
+          return;
+
+        // If we're dragging a toolbar button, let's prepend the tabs toolbar
+        // with that button, and then bail out.
+        let buttonId = dt.mozGetDataAt("text/toolbarwrapper-id/messengerWindow", 0);
+
+        if (buttonId != null) {
+          event.preventDefault();
+          event.stopPropagation();
+
+          let evt = document.createEvent("DragEvent");
+          evt.initDragEvent("drop", true, true, window, 0, 0, 0, 0, 0,
+            false, false, false, false, 0, null,
+            event.dataTransfer);
+
+          if (this.mToolbar.firstChild) {
+            this.mToolbar.firstChild.dispatchEvent(evt);
+          } else {
+            this.mToolbar.dispatchEvent(evt);
+          }
+
+          return;
+        }
+
+        let draggedTab = dt.mozGetDataAt("application/x-moz-tabmail-tab", 0);
+
+        if (!draggedTab)
+          return;
+
+        event.stopPropagation();
+        this._tabDropIndicator.setAttribute("hidden", "true");
+
+        // Is the tab one of our children?
+        if (this.tabmail.tabContainer.getIndexOfItem(draggedTab) == -1) {
+          // It's a tab from an other window, so we have to trigger session
+          // restore to get our tab
+
+          let tabmail2 = draggedTab.ownerDocument.getElementById("tabmail");
+          if (!tabmail2)
+            return;
+
+          let draggedJson = dt.mozGetDataAt("application/x-moz-tabmail-json", 0);
+          if (!draggedJson)
+            return;
+
+          draggedJson = JSON.parse(draggedJson);
+
+          // Some tab exist only once, so we have to gamble a bit. We close
+          // the tab and try to reopen it. If something fails the tab is gone.
+
+          tabmail2.closeTab(draggedTab, true);
+
+          if (!this.tabmail.restoreTab(draggedJson))
+            return;
+
+          draggedTab = this.tabmail.tabContainer.allTabs[
+            this.tabmail.tabContainer.allTabs.length - 1];
+        }
+
+        let idx = this._getDropIndex(event);
+
+        // Fix the DropIndex in case it points to tab that can't be closed
+        let tabInfo = this.tabmail.tabInfo;
+        while ((idx < tabInfo.length) && !(tabInfo[idx].canClose)) {
+          idx++;
+        }
+
+        this.tabmail.moveTabTo(draggedTab, idx);
+
+        this.tabmail.switchToTab(draggedTab);
+        this.tabmail.updateCurrentTab();
+      });
+
+      this.addEventListener("dragend", (event) => {
+        // Note: while this case is correctly handled here, this event
+        // isn't dispatched when the tab is moved within the tabstrip,
+        // see bug 460801.
+
+        // The user pressed ESC to cancel the drag, or the drag succeeded.
+        let dt = event.dataTransfer;
+        if ((dt.mozUserCancelled) || (dt.dropEffect != "none"))
+          return;
+
+        // Disable detach within the browser toolbox.
+        let eX = event.screenX;
+        let wX = window.screenX;
+
+        // Check if the drop point is horizontally within the window.
+        if (eX > wX && eX < (wX + window.outerWidth)) {
+          let bo = this.arrowScrollbox;
+          // Also avoid detaching if the the tab was dropped too close to
+          // the tabbar (half a tab).
+          let endScreenY = bo.screenY + 1.5 * bo.getBoundingClientRect().height;
+          let eY = event.screenY;
+
+          if (eY < endScreenY && eY > window.screenY)
+            return;
+        }
+
+        // User wants to deatach tab from window...
+        if (dt.mozItemCount != 1)
+          return;
+
+        let draggedTab = dt.mozGetDataAt("application/x-moz-tabmail-tab", 0);
+
+        if (!draggedTab)
+          return;
+
+        this.tabmail.replaceTabWithWindow(draggedTab);
+      });
+
+      this.addEventListener("dragexit", (event) => {
+        this._dragTime = 0;
+
+        this._tabDropIndicator.setAttribute("hidden", "true");
+        event.stopPropagation();
+      });
+
+      this.addEventListener("click", (event) => {
+        if (event.button != 0) {
+          return;
+        }
+
+        if (!event.originalTarget.classList.contains("tabs-closebutton")) {
+          return;
+        }
+
+        let tabbedBrowser = document.getElementById("tabmail");
+        if (this.localName == "tab") {
+          // The only sequence in which a second click event (i.e. dblclik)
+          // can be dispatched on an in-tab close button is when it is shown
+          // after the first click (i.e. the first click event was dispatched
+          // on the tab). This happens when we show the close button only on
+          // the active tab. (bug 352021)
+          // The only sequence in which a third click event can be dispatched
+          // on an in-tab close button is when the tab was opened with a
+          // double click on the tabbar. (bug 378344)
+          // In both cases, it is most likely that the close button area has
+          // been accidentally clicked, therefore we do not close the tab.
+          if (event.detail > 1)
+            return;
+
+          tabbedBrowser.removeTabByNode(this);
+          tabbedBrowser._blockDblClick = true;
+          let tabContainer = tabbedBrowser.tabContainer;
+
+          // XXXmano hack (see bug 343628):
+          // Since we're removing the event target, if the user
+          // double-clicks this button, the dblclick event will be dispatched
+          // with the tabbar as its event target (and explicit/originalTarget),
+          // which treats that as a mouse gesture for opening a new tab.
+          // In this context, we're manually blocking the dblclick event
+          // (see onTabBarDblClick).
+          let clickedOnce = false;
+          let enableDblClick = function enableDblClick(event) {
+            let target = event.originalTarget;
+            if (target.className == "tab-close-button")
+              target._ignoredClick = true;
+            if (!clickedOnce) {
+              clickedOnce = true;
+              return;
+            }
+            tabContainer._blockDblClick = false;
+            tabContainer.removeEventListener("click", enableDblClick, true);
+          };
+          tabContainer.addEventListener("click", enableDblClick, true);
+        } else { // "tabs"
+          tabbedBrowser.removeCurrentTab();
+        }
+      });
+
+      this.addEventListener("dblclick", (event) => {
+        if (event.button != 0) {
+          return;
+        }
+
+        // for the one-close-button case
+        event.stopPropagation();
+      }, true);
+    }
+
+    connectedCallback() {
+      if (this.delayConnectedCallback()) {
+        return;
+      }
+      super.connectedCallback();
+
+      this.tabmail = document.getElementById("tabmail");
+
+      this.arrowScrollboxWidth = 0;
+
+      this.arrowScrollbox = this.querySelector("arrowscrollbox");
+
+      this.arrowScrollboxClosebutton = this.querySelector(".tabs-closebutton-box");
+
+      this.mToolbar = document.getElementById(this.getAttribute("tabtoolbar"));
+
+      this.mCollapseToolbar = document.getElementById(this.getAttribute("collapsetoolbar"));
+
+      // @implements {nsIObserver}
+      this._prefObserver = (subject, topic, data) => {
+        if (topic == "nsPref:changed") {
+          subject.QueryInterface(Ci.nsIPrefBranch);
+          switch (data) {
+            case "mail.tabs.closeButtons":
+              this.mCloseButtons = subject.getIntPref("mail.tabs.closeButtons");
+              this._updateCloseButtons();
+              break;
+            case "mail.tabs.autoHide":
+              this.mAutoHide = subject.getBoolPref("mail.tabs.autoHide");
+              break;
+          }
+        }
+      };
+
+      this._tabDropIndicator = this.querySelector(".tab-drop-indicator");
+
+      this._dragOverDelay = 350;
+
+      this._dragTime = 0;
+
+      this.mTabMinWidth = 100;
+
+      this.mTabMaxWidth = 250;
+
+      this.mTabClipWidth = 140;
+
+      this.mCloseButtons = 1;
+
+      this._mAutoHide = false;
+
+      this.mAllTabsButton = document.getElementById(this.getAttribute("alltabsbutton"));
+      this.mAllTabsPopup = this.mAllTabsButton.menu;
+
+      // this.mAllTabsBoxAnimate = document.getAnonymousElementByAttribute(this,
+      //  "anonid",
+      //  "alltabs-box-animate");
+      // TODO ^^^ alltabs-box-animate seems to be dead code. remove all refs?
+
+      this.mDownBoxAnimate = this.arrowScrollbox._scrollButtonDownBoxAnimate;
+
+      this._animateTimer = null;
+
+      this._animateStep = -1;
+
+      this._animateDelay = 25;
+
+      this._animatePercents = [1.00, 0.85, 0.80, 0.75, 0.71, 0.68, 0.65, 0.62, 0.59, 0.57,
+        0.54, 0.52, 0.50, 0.47, 0.45, 0.44, 0.42, 0.40, 0.38, 0.37,
+        0.35, 0.34, 0.32, 0.31, 0.30, 0.29, 0.28, 0.27, 0.26, 0.25,
+        0.24, 0.23, 0.23, 0.22, 0.22, 0.21, 0.21, 0.21, 0.20, 0.20,
+        0.20, 0.20, 0.20, 0.20, 0.20, 0.20, 0.19, 0.19, 0.19, 0.18,
+        0.18, 0.17, 0.17, 0.16, 0.15, 0.14, 0.13, 0.11, 0.09, 0.06,
+      ];
+
+      this.mTabMinWidth = Services.prefs.getIntPref("mail.tabs.tabMinWidth");
+      this.mTabMaxWidth = Services.prefs.getIntPref("mail.tabs.tabMaxWidth");
+      this.mTabClipWidth = Services.prefs.getIntPref("mail.tabs.tabClipWidth");
+      this.mCloseButtons = Services.prefs.getIntPref("mail.tabs.closeButtons");
+      this.mAutoHide = Services.prefs.getBoolPref("mail.tabs.autoHide");
+
+      if (this.mAutoHide)
+        this.mCollapseToolbar.collapsed = true;
+
+      this.arrowScrollbox.firstChild.minWidth = this.mTabMinWidth;
+      this.arrowScrollbox.firstChild.maxWidth = this.mTabMaxWidth;
+      this._updateCloseButtons();
+
+      Services.prefs.addObserver("mail.tabs.", this._prefObserver);
+
+      window.addEventListener("resize", this);
+
+      // Listen to overflow/underflow events on the tabstrip,
+      // we cannot put these as xbl handlers on the entire binding because
+      // they would also get called for the all-tabs popup scrollbox.
+      // Also, we can't rely on event.target because these are all
+      // anonymous nodes.
+      this.arrowScrollbox.addEventListener("overflow", this);
+      this.arrowScrollbox.addEventListener("underflow", this);
+
+      this.addEventListener("select", (event) => {
+        this._handleTabSelect();
+
+        if (!("updateCurrentTab" in this.tabmail) ||
+          event.target.localName != "tabs")
+          return;
+
+        this.tabmail.updateCurrentTab();
+      });
+
+      this.addEventListener("TabSelect", (event) => { this._handleTabSelect(); });
+    }
+
+    get tabbox() {
+      return document.getElementById("tabmail-tabbox");
+    }
+
+    // Accessor for tabs.
+    get allTabs() {
+      if (!this.arrowScrollbox) {
+        return [];
+      }
+
+      return Array.from(this.arrowScrollbox.children);
+    }
+
+    appendChild(tab) {
+      return this.insertBefore(tab, null);
+    }
+
+    insertBefore(tab, node) {
+      if (!this.arrowScrollbox) {
+        return;
+      }
+
+      if (node == null) {
+        this.arrowScrollbox.appendChild(tab);
+        return;
+      }
+
+      this.arrowScrollbox.insertBefore(tab, node);
+    }
+
+    set mAutoHide(val) {
+      if (val != this._mAutoHide) {
+        if (this.allTabs.length == 1)
+          this.mCollapseToolbar.collapsed = val;
+        this._mAutoHide = val;
+      }
+      return val;
+    }
+
+    get mAutoHide() {
+      return this._mAutoHide;
+    }
+
+    set selectedIndex(val) {
+      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.
+        let event = document.createEvent("Events");
+        event.initEvent("select", true, true);
+        this.dispatchEvent(event);
+      }
+
+      return val;
+    }
+
+    get selectedIndex() {
+      return this.__proto__.__proto__.__lookupGetter__("selectedIndex").call(this);
+    }
+
+    _updateCloseButtons() {
+      // 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) {
+        case 0:
+          this.setAttribute("closebuttons", "activetab");
+          break;
+        case 1:
+          let width = this.arrowScrollbox.firstChild.getBoundingClientRect().width;
+          // 0 width is an invalid value and indicates
+          // an item without display, so ignore.
+          if (width > this.mTabClipWidth || width == 0)
+            this.setAttribute("closebuttons", "alltabs");
+          else
+            this.setAttribute("closebuttons", "activetab");
+          break;
+        case 2:
+        case 3:
+          this.setAttribute("closebuttons", "noclose");
+          break;
+      }
+      this.arrowScrollboxClosebutton.collapsed = this.mCloseButtons != 3;
+    }
+
+    _handleTabSelect() {
+      this.arrowScrollbox.ensureElementIsVisible(this.selectedItem);
+    }
+
+    handleEvent(aEvent) {
+      switch (aEvent.type) {
+        case "overflow":
+          this.arrowScrollbox.ensureElementIsVisible(this.selectedItem);
+          break;
+        case "underflow":
+          break;
+        case "resize":
+          let width = this.arrowScrollbox.getBoundingClientRect().width;
+          if (width != this.arrowScrollboxWidth) {
+            this._updateCloseButtons();
+            // XXX without this line the tab bar won't budge
+            this.arrowScrollbox.scrollByPixels(1);
+            this._handleTabSelect();
+            this.arrowScrollboxWidth = width;
+          }
+          break;
+      }
+    }
+
+    _stopAnimation() {
+      if (this._animateStep != -1) {
+        if (this._animateTimer)
+          this._animateTimer.cancel();
+
+        this._animateStep = -1;
+        this.mAllTabsBoxAnimate.style.opacity = 0.0;
+        this.mDownBoxAnimate.style.opacity = 0.0;
+      }
+    }
+
+    _notifyBackgroundTab(aTab) {
+      let tsbo = this.arrowScrollbox;
+      let tsboStart = tsbo.screenX;
+      let tsboEnd = tsboStart + tsbo.getBoundingClientRect().width;
+
+      let ctboStart = aTab.screenX;
+      let ctboEnd = ctboStart + aTab.getBoundingClientRect().width;
+
+      // only start the flash timer if the new tab (which was loaded in
+      // the background) is not completely visible
+      if (tsboStart > ctboStart || ctboEnd > tsboEnd) {
+        this._animateStep = 0;
+
+        if (!this._animateTimer)
+          this._animateTimer =
+          Cc["@mozilla.org/timer;1"]
+          .createInstance(Ci.nsITimer);
+        else
+          this._animateTimer.cancel();
+
+        this._animateTimer.initWithCallback(this,
+          this._animateDelay,
+          Ci.nsITimer.TYPE_REPEATING_SLACK);
+      }
+    }
+
+    notify(aTimer) {
+      if (!document)
+        aTimer.cancel();
+
+      let percent = this._animatePercents[this._animateStep];
+      this.mAllTabsBoxAnimate.style.opacity = percent;
+      this.mDownBoxAnimate.style.opacity = percent;
+
+      if (this._animateStep < (this._animatePercents.length - 1))
+        this._animateStep++;
+      else
+        this._stopAnimation();
+    }
+
+    _getDragTargetTab(event) {
+      let tab = event.target;
+      while (tab && tab.localName != "tab") {
+        tab = tab.parentNode;
+      }
+
+      if ((event.type != "drop") && (event.type != "dragover"))
+        return tab;
+
+      let tabRect = tab.getBoundingClientRect();
+      if (event.screenX < tab.screenX + tabRect.width * .25)
+        return null;
+
+      if (event.screenX > tab.screenX + tabRect.width * .75)
+        return null;
+
+      return tab;
+    }
+
+    _getDropIndex(event) {
+      let tabs = this.allTabs;
+
+      if (window.getComputedStyle(this).direction == "ltr") {
+        for (let i = 0; i < tabs.length; i++)
+          if (event.screenX < (tabs[i].screenX + (tabs[i].getBoundingClientRect().width / 2)))
+            return i;
+      } else {
+        for (let i = 0; i < tabs.length; i++)
+          if (event.screenX > (tabs[i].screenX + (tabs[i].getBoundingClientRect().width / 2)))
+            return i;
+      }
+
+      return tabs.length;
+    }
+
+    disconnectedCallback() {
+      Services.prefs.removeObserver("mail.tabs.", this._prefObserver);
+
+      // Release timer to avoid reference cycles.
+      if (this._animateTimer) {
+        this._animateTimer.cancel();
+        this._animateTimer = null;
+      }
+
+      this.arrowScrollbox.removeEventListener("overflow", this);
+      this.arrowScrollbox.removeEventListener("underflow", this);
+    }
+  }
+
+  MozXULElement.implementCustomInterface(MozTabmailTabs, [Ci.nsITimerCallback]);
+  customElements.define("tabmail-tabs", MozTabmailTabs, { extends: "tabs" });
+}
--- a/mail/base/content/tabmail.css
+++ b/mail/base/content/tabmail.css
@@ -1,16 +1,12 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#tabmail-tabs {
-  -moz-binding: url("chrome://messenger/content/tabmail.xml#tabmail-tabs");
-}
-
 .tabs-alltabs-popup {
   -moz-binding: url("chrome://messenger/content/tabmail.xml#tabmail-alltabs-popup");
 }
 
 .tab-label-container {
   overflow: hidden;
 }
 
@@ -25,28 +21,29 @@
 .tab-label {
   white-space: nowrap;
 }
 
 .tab-close-button {
   display: none;
 }
 
-#tabmail-tabs > .tabmail-tab:first-child > .tab-stack > .tab-content >
+#tabmail-tabs > arrowscrollbox > .tabmail-tab:first-child > .tab-stack > .tab-content >
 .tab-close-button {
   visibility: collapse;
 }
 
 #tabmail-tabs:not([closebuttons="noclose"]):not([closebuttons="closeatend"]) >
-.tabmail-tab[selected="true"]:not(:only-child) > .tab-stack > .tab-content > .tab-close-button {
+arrowscrollbox > .tabmail-tab[selected="true"]:not(:only-child) > .tab-stack >
+.tab-content > .tab-close-button {
   display: -moz-box;
 }
 
-#tabmail-tabs[closebuttons="alltabs"] > .tabmail-tab:not(:only-child) >
-.tab-stack > .tab-content > .tab-close-button {
+#tabmail-tabs[closebuttons="alltabs"] > arrowscrollbox >
+.tabmail-tab:not(:only-child) > .tab-stack > .tab-content > .tab-close-button {
   display: -moz-box;
 }
 
 .tab-drop-indicator-box {
   -moz-box-align: end;
 }
 
 .tab-drop-indicator {
--- a/mail/base/content/tabmail.xml
+++ b/mail/base/content/tabmail.xml
@@ -317,18 +317,23 @@
         new Set();
       </field>
       <field name="unrestoredTabs" readonly="true">
         [];
       </field>
       <method name="createTooltip">
         <parameter name="event"/>
         <body><![CDATA[
+          let tab = document.tooltipNode ? document.tooltipNode.closest("tab") : null;
+          if (!tab) {
+            event.preventDefault();
+            return;
+          }
           event.stopPropagation();
-          let tab = document.tab;
+
           if (tab.localName != "tab" || this.tabContainer.draggedTab) {
             event.preventDefault();
             return;
           }
           event.target.setAttribute("label", tab.mOverCloseButton ?
                                              tab.firstChild.getAttribute("closetabtext") :
                                              tab.getAttribute("label"));
 
@@ -417,31 +422,31 @@
         <parameter name="aDefaultToCurrent"/>
         <body><![CDATA[
           let iTab, tab, tabNode;
           if (aTabIndexNodeOrInfo == null) {
             if (!aDefaultToCurrent)
               throw new Error("You need to specify a tab!");
             iTab = this.tabContainer.selectedIndex;
             return [iTab, this.tabInfo[iTab],
-                    this.tabContainer.childNodes[iTab]];
+                    this.tabContainer.allTabs[iTab]];
           }
 
           if (typeof(aTabIndexNodeOrInfo) == "number") {
             iTab = aTabIndexNodeOrInfo;
-            tabNode = this.tabContainer.childNodes[iTab];
+            tabNode = this.tabContainer.allTabs[iTab];
             tab = this.tabInfo[iTab];
           } else if (aTabIndexNodeOrInfo.tagName && aTabIndexNodeOrInfo.tagName == "tab") {
             tabNode = aTabIndexNodeOrInfo;
             iTab = this.tabContainer.getIndexOfItem(tabNode);
             tab = this.tabInfo[iTab];
           } else {
             tab = aTabIndexNodeOrInfo;
             iTab = this.tabInfo.indexOf(tab);
-            tabNode = (iTab >= 0) ? this.tabContainer.childNodes[iTab] : null;
+            tabNode = (iTab >= 0) ? this.tabContainer.allTabs[iTab] : null;
           }
 
           return [iTab, tab, tabNode];
         ]]></body>
       </method>
       <method name="openFirstTab">
         <body><![CDATA[
           // From the moment of creation, our XBL binding already has a visible
@@ -563,23 +568,23 @@
           // so that if the new tab is closed without switching, we can switch
           // back to the opener tab.
           if (disregardOpener)
             this.mLastTabOpener = null;
           else
             this.mLastTabOpener = oldTab;
 
           // the order of the following statements is important
-          this.tabInfo[this.tabContainer.childNodes.length - 1] = tab;
+          this.tabInfo[this.tabContainer.allTabs.length - 1] = tab;
           if (!background) {
             this.currentTabInfo = tab;
             // this has a side effect of calling updateCurrentTab, but our
             //  setting currentTabInfo above will cause it to take no action.
             this.tabContainer.selectedIndex =
-              this.tabContainer.childNodes.length - 1;
+              this.tabContainer.allTabs.length - 1;
           }
 
           // 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.createXULElement(tab.mode.tabType.perTabPanel);
             } else {
@@ -743,17 +748,17 @@
 
           if (!history.tab)
             return;
 
           if (!this.restoreTab(JSON.parse(history.tab)))
             return;
 
           let idx = Math.min(history.idx, this.tabInfo.length);
-          let tab = this.tabContainer.childNodes[this.tabInfo.length - 1];
+          let tab = this.tabContainer.allTabs[this.tabInfo.length - 1];
           this.moveTabTo(tab, idx);
 
           this.switchToTab(tab);
 
         ]]></body>
       </method>
       <method name="closeTab">
         <parameter name="aOptTabIndexNodeOrInfo"/>
@@ -806,17 +811,17 @@
             tab.mode.tabs.splice(tab.mode.tabs.indexOf(tab), 1);
             tabNode.remove();
 
             if (this.tabContainer.selectedIndex == -1) {
               if (this.mLastTabOpener && this.tabInfo.includes(this.mLastTabOpener)) {
                 this.tabContainer.selectedIndex = this.tabInfo.indexOf(this.mLastTabOpener);
               } else {
                 this.tabContainer.selectedIndex =
-                  (iTab == this.tabContainer.childNodes.length) ? iTab - 1 : iTab;
+                  (iTab == this.tabContainer.allTabs.length) ? iTab - 1 : iTab;
               }
             }
 
             // Clear the last tab opener - we don't need this anymore.
             this.mLastTabOpener = null;
 
             if (this.currentTabInfo == tab)
               this.updateCurrentTab();
@@ -825,17 +830,17 @@
               tab.panel.remove();
               delete tab.panel;
 
               // Ensure current tab is still selected and displayed in the
               // panelContainer.
               this.panelContainer.selectedPanel =
                 this.currentTabInfo.panel || this.currentTabInfo.mode.tabType.panel;
             }
-            if (this.tabContainer.childNodes.length == 1 &&
+            if (this.tabContainer.allTabs.length == 1 &&
                 this.tabContainer.mAutoHide)
               this.tabContainer.mCollapseToolbar.collapsed = true;
           ]]>
         </body>
       </method>
       <method name="removeTabByNode">
         <parameter name="aTabNode"/>
         <body>
@@ -939,17 +944,17 @@
 
           // as we removed items, we might need to update indices
           if (oldIdx < aIndex) {
             aIndex--;
           }
 
           // Read it into tabInfo and the tabContainer
           this.tabInfo.splice(aIndex, 0, tab);
-          this.tabContainer.insertBefore(tabNode, this.tabContainer.childNodes[aIndex]);
+          this.tabContainer.insertBefore(tabNode, this.tabContainer.allTabs[aIndex]);
 
           // Now it's getting a bit ugly, as tabModes stores redundant
           // information we need to get it in sync with tabInfo.
           //
           // As tabModes.tabs is a subset of tabInfo, every tab can be mapped
           // to a tabInfo index. So we check for each tab in tabModes if it is
           // directly in front of our moved tab. We do this by looking up the
           // index in tabInfo and compare it with the moved tab's index. If we
@@ -1224,17 +1229,17 @@
             }
           }
           return null;
         ]]></body>
       </method>
       <method name="removeCurrentTab">
         <body><![CDATA[
           this.removeTabByNode(
-            this.tabContainer.childNodes[this.tabContainer.selectedIndex]);
+            this.tabContainer.allTabs[this.tabContainer.selectedIndex]);
         ]]></body>
       </method>
       <method name="switchToTab">
         <parameter name="aTabIndexNodeOrInfo"/>
         <body>
           <![CDATA[
             let [iTab] =
               this._getTabContextForTabbyThing(aTabIndexNodeOrInfo, false);
@@ -1325,17 +1330,17 @@
         <parameter name="aTabNodeOrInfo"/>
         <body>
           <![CDATA[
             let [iTab, tab] =
               this._getTabContextForTabbyThing(aTabNodeOrInfo, true);
 
             if (tab) {
               let tabNode =
-                this.tabContainer.childNodes[iTab];
+                this.tabContainer.allTabs[iTab];
 
               let titleChangeFunc = tab.mode.onTitleChanged ||
                                     tab.mode.tabType.onTitleChanged;
               if (titleChangeFunc) {
                 titleChangeFunc.call(tab.mode.tabType, tab, tabNode);
               }
 
               let defaultTabTitle = document.documentElement.getAttribute("defaultTabTitle");
@@ -1379,17 +1384,17 @@
         <parameter name="aTabNodeOrInfo"/>
         <parameter name="aIcon"/>
         <body>
           <![CDATA[
             let [iTab, tab] =
               this._getTabContextForTabbyThing(aTabNodeOrInfo, true);
 
             if (tab) {
-              let tabNode = this.tabContainer.childNodes[iTab];
+              let tabNode = this.tabContainer.allTabs[iTab];
               let oldIcon = tabNode.getAttribute("image");
 
               if (oldIcon != aIcon && !tab.beforeTabOpen) {
                 let evt = new CustomEvent("TabAttrModified", {
                   bubbles: true,
                   cancelable: false,
                   detail: { changed: ["image"], tabInfo: tab },
                 });
@@ -1784,854 +1789,16 @@
                                     .chromeEventHandler;
           this._callTabListeners("onRefreshAttempted", [browser, ...arguments]);
         ]]></body>
       </method>
 
     </implementation>
   </binding>
 
-  <binding id="tabmail-tabs"
-           extends="chrome://global/content/bindings/tabbox.xml#tabs">
-    <content context="toolbar-context-menu">
-      <xul:vbox flex="1">
-        <xul:hbox>
-          <xul:hbox class="tab-drop-indicator-box">
-            <xul:image class="tab-drop-indicator"
-                       anonid="tab-drop-indicator"
-                       collapsed="true"/>
-          </xul:hbox>
-          <xul:arrowscrollbox class="tabmail-arrowscrollbox"
-                              anonid="arrowscrollbox"
-                              orient="horizontal"
-                              flex="1"
-                              style="min-width: 1px;"
-                              clicktoscroll="true">
-            <children includes="tab"/>
-          </xul:arrowscrollbox>
-          <children/>
-          <xul:hbox class="tabs-closebutton-box"
-                    anonid="tabstrip-closebutton"
-                    align="center"
-                    pack="end">
-            <xul:image class="close-icon tabs-closebutton"/>
-          </xul:hbox>
-        </xul:hbox>
-      </xul:vbox>
-      <xul:stack align="center"
-                 pack="end"
-                 class="scrollbutton-down-stack">
-        <xul:hbox flex="1"
-                  class="scrollbutton-down-box"
-                  collapsed="true"
-                  anonid="down-box"/>
-        <xul:hbox flex="1"
-                  class="scrollbutton-down-box-animate"
-                  collapsed="true"
-                  anonid="down-box-animate"/>
-      </xul:stack>
-    </content>
-
-    <implementation implements="nsITimerCallback">
-      <constructor>
-        <![CDATA[
-          const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
-
-          this.mTabMinWidth = Services.prefs.getIntPref("mail.tabs.tabMinWidth");
-          this.mTabMaxWidth = Services.prefs.getIntPref("mail.tabs.tabMaxWidth");
-          this.mTabClipWidth = Services.prefs.getIntPref("mail.tabs.tabClipWidth");
-          this.mCloseButtons = Services.prefs.getIntPref("mail.tabs.closeButtons");
-          this.mAutoHide = Services.prefs.getBoolPref("mail.tabs.autoHide");
-
-          if (this.mAutoHide)
-            this.mCollapseToolbar.collapsed = true;
-
-          this.firstChild.minWidth = this.mTabMinWidth;
-          this.firstChild.maxWidth = this.mTabMaxWidth;
-          this._updateCloseButtons();
-          this._initializeArrowScrollbox();
-
-          Services.prefs.addObserver("mail.tabs.", this._prefObserver);
-
-          window.addEventListener("resize", this);
-
-          // Listen to overflow/underflow events on the tabstrip,
-          // we cannot put these as xbl handlers on the entire binding because
-          // they would also get called for the all-tabs popup scrollbox.
-          // Also, we can't rely on event.target because these are all
-          // anonymous nodes.
-          this.arrowScrollbox.addEventListener("overflow", this);
-          this.arrowScrollbox.addEventListener("underflow", this);
-        ]]>
-      </constructor>
-
-      <destructor>
-        <![CDATA[
-          const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
-
-          Services.prefs.removeObserver("mail.tabs.", this._prefObserver);
-
-          // Release timer to avoid reference cycles.
-          if (this._animateTimer) {
-            this._animateTimer.cancel();
-            this._animateTimer = null;
-          }
-
-          this.arrowScrollbox.removeEventListener("overflow", this);
-          this.arrowScrollbox.removeEventListener("underflow", this);
-        ]]>
-      </destructor>
-
-      <field name="tabmail" readonly="true">
-        document.getElementById("tabmail");
-      </field>
-
-      <field name="tabbox" readonly="true">
-        this.tabmail.tabbox;
-      </field>
-
-      <field name="arrowScrollboxWidth">0</field>
-
-      <field name="arrowScrollbox">
-        document.getAnonymousElementByAttribute(this, "anonid", "arrowscrollbox");
-      </field>
-
-      <field name="arrowScrollboxClosebutton">
-        document.getAnonymousElementByAttribute(this, "anonid", "tabstrip-closebutton");
-      </field>
-
-      <field name="_scrollButtonDownBox">
-        document.getAnonymousElementByAttribute(this, "anonid", "down-box");
-      </field>
-
-      <field name="_scrollButtonDownBoxAnimate">
-        document.getAnonymousElementByAttribute(this, "anonid", "down-box-animate");
-      </field>
-
-      <field name="mToolbar">
-        document.getElementById(this.getAttribute("tabtoolbar"));
-      </field>
-
-      <field name="mCollapseToolbar">
-        document.getElementById(this.getAttribute("collapsetoolbar"));
-      </field>
-
-      <field name="_prefObserver"><![CDATA[
-        ({
-        tabbox: this,
-
-        observe(subject, topic, data) {
-          if (topic == "nsPref:changed") {
-            subject.QueryInterface(Ci.nsIPrefBranch);
-            switch (data) {
-            case "mail.tabs.closeButtons":
-              this.tabbox.mCloseButtons = subject.getIntPref("mail.tabs.closeButtons");
-              this.tabbox._updateCloseButtons();
-              break;
-            case "mail.tabs.autoHide":
-              this.tabbox.mAutoHide = subject.getBoolPref("mail.tabs.autoHide");
-              break;
-            }
-          }
-        },
-
-        QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
-        });
-        ]]>
-      </field>
-
-      <field name="_tabDropIndicator">
-        document.getAnonymousElementByAttribute(this, "anonid",
-                                                "tab-drop-indicator");
-      </field>
-
-      <field name="_dragOverDelay">350</field>
-      <field name="_dragTime">0</field>
-
-      <field name="mTabMinWidth">100</field>
-      <field name="mTabMaxWidth">250</field>
-      <field name="mTabClipWidth">140</field>
-      <field name="mCloseButtons">1</field>
-      <field name="_mAutoHide">false</field>
-
-      <property name="mAutoHide" onget="return this._mAutoHide;">
-        <setter><![CDATA[
-          if (val != this._mAutoHide) {
-            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="_initializeArrowScrollbox">
-        <body><![CDATA[
-          let arrowScrollbox = this.arrowScrollbox;
-          arrowScrollbox.addEventListener("underflow", event => {
-            // filter underflow events which were dispatched on nested scrollboxes
-            if (event.target != arrowScrollbox)
-              return;
-
-            // Ignore vertical events.
-            if (event.detail == 0) {
-              return;
-            }
-
-            arrowScrollbox.setAttribute("notoverflowing", "true");
-            let alltabsButton = document.getElementById("alltabs-button");
-            alltabsButton.setAttribute("hidden", "true");
-          }, true);
-
-          arrowScrollbox.addEventListener("overflow", event => {
-            // filter underflow events which were dispatched on nested scrollboxes
-            if (event.target != arrowScrollbox)
-              return;
-
-            // Ignore vertical events.
-            if (event.detail == 0) {
-              return;
-            }
-
-            arrowScrollbox.removeAttribute("notoverflowing");
-            let alltabsButton = document.getElementById("alltabs-button");
-            alltabsButton.removeAttribute("hidden");
-          });
-
-          arrowScrollbox.addEventListener("UpdatedScrollButtonsDisabledState", event => {
-            // filter underflow events which were dispatched on nested scrollboxes
-            if (event.target != arrowScrollbox)
-              return;
-
-            // fix for bug #352353
-            // unlike the scrollup button on the tab strip (which is a
-            // simple toolbarbutton) the scrolldown button is
-            // a more complicated stack of boxes and a toolbarbutton
-            // so that we can animate when a tab is opened offscreen.
-            // in order to style the box with the actual background image
-            // we need to manually set the disable state to match the
-            // disable state of the toolbarbutton.
-            arrowScrollbox._scrollButtonDownBox
-                .setAttribute("disabled", arrowScrollbox._scrollButtonDown.disabled);
-          });
-        ]]></body>
-      </method>
-
-      <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) {
-          case 0:
-            this.setAttribute("closebuttons", "activetab");
-            break;
-          case 1:
-            var width = this.firstChild.getBoundingClientRect().width;
-            // 0 width is an invalid value and indicates
-            // an item without display, so ignore.
-            if (width > this.mTabClipWidth || width == 0)
-              this.setAttribute("closebuttons", "alltabs");
-            else
-              this.setAttribute("closebuttons", "activetab");
-            break;
-          case 2:
-          case 3:
-            this.setAttribute("closebuttons", "noclose");
-            break;
-          }
-          this.arrowScrollboxClosebutton.collapsed = this.mCloseButtons != 3;
-        ]]></body>
-      </method>
-
-      <method name="_handleTabSelect">
-        <body><![CDATA[
-          this.arrowScrollbox.ensureElementIsVisible(this.selectedItem);
-        ]]></body>
-      </method>
-
-      <method name="handleEvent">
-        <parameter name="aEvent"/>
-        <body><![CDATA[
-          switch (aEvent.type) {
-            case "overflow":
-              this.arrowScrollbox.ensureElementIsVisible(this.selectedItem);
-              break;
-            case "underflow":
-              break;
-            case "resize":
-              var width = this.arrowScrollbox.getBoundingClientRect().width;
-              if (width != this.arrowScrollboxWidth) {
-                this._updateCloseButtons();
-                // XXX without this line the tab bar won't budge
-                this.arrowScrollbox.scrollByPixels(1);
-                this._handleTabSelect();
-                this.arrowScrollboxWidth = width;
-              }
-              break;
-          }
-        ]]></body>
-      </method>
-
-      <field name="mAllTabsPopup">
-        this.mAllTabsButton.menu;
-      </field>
-
-      <field name="mAllTabsBoxAnimate">
-        document.getAnonymousElementByAttribute(this,
-                                                "anonid",
-                                                "alltabs-box-animate");
-      </field>
-
-      <field name="mDownBoxAnimate">
-        this.arrowScrollbox._scrollButtonDownBoxAnimate;
-      </field>
-
-      <field name="mAllTabsButton">
-        document.getElementById(this.getAttribute("alltabsbutton"));
-      </field>
-
-      <field name="_animateTimer">null</field>
-      <field name="_animateStep">-1</field>
-      <field name="_animateDelay">25</field>
-      <field name="_animatePercents">
-       [1.00, 0.85, 0.80, 0.75, 0.71, 0.68, 0.65, 0.62, 0.59, 0.57,
-        0.54, 0.52, 0.50, 0.47, 0.45, 0.44, 0.42, 0.40, 0.38, 0.37,
-        0.35, 0.34, 0.32, 0.31, 0.30, 0.29, 0.28, 0.27, 0.26, 0.25,
-        0.24, 0.23, 0.23, 0.22, 0.22, 0.21, 0.21, 0.21, 0.20, 0.20,
-        0.20, 0.20, 0.20, 0.20, 0.20, 0.20, 0.19, 0.19, 0.19, 0.18,
-        0.18, 0.17, 0.17, 0.16, 0.15, 0.14, 0.13, 0.11, 0.09, 0.06]
-      </field>
-
-      <method name="_stopAnimation">
-        <body><![CDATA[
-          if (this._animateStep != -1) {
-            if (this._animateTimer)
-              this._animateTimer.cancel();
-
-            this._animateStep = -1;
-            this.mAllTabsBoxAnimate.style.opacity = 0.0;
-            this.mDownBoxAnimate.style.opacity = 0.0;
-          }
-        ]]></body>
-      </method>
-
-      <method name="_notifyBackgroundTab">
-        <parameter name="aTab"/>
-        <body><![CDATA[
-          var tsbo = this.arrowScrollbox;
-          var tsboStart = tsbo.screenX;
-          var tsboEnd = tsboStart + tsbo.getBoundingClientRect().width;
-
-          var ctboStart = aTab.screenX;
-          var ctboEnd = ctboStart + aTab.getBoundingClientRect().width;
-
-          // only start the flash timer if the new tab (which was loaded in
-          // the background) is not completely visible
-          if (tsboStart > ctboStart || ctboEnd > tsboEnd) {
-            this._animateStep = 0;
-
-            if (!this._animateTimer)
-              this._animateTimer =
-                Cc["@mozilla.org/timer;1"]
-                  .createInstance(Ci.nsITimer);
-            else
-               this._animateTimer.cancel();
-
-            this._animateTimer.initWithCallback(this,
-                         this._animateDelay,
-                         Ci.nsITimer.TYPE_REPEATING_SLACK);
-          }
-        ]]></body>
-      </method>
-
-      <method name="notify">
-        <parameter name="aTimer"/>
-        <body><![CDATA[
-          if (!document)
-            aTimer.cancel();
-
-          var percent = this._animatePercents[this._animateStep];
-          this.mAllTabsBoxAnimate.style.opacity = percent;
-          this.mDownBoxAnimate.style.opacity = percent;
-
-          if (this._animateStep < (this._animatePercents.length - 1))
-            this._animateStep++;
-          else
-            this._stopAnimation();
-        ]]></body>
-      </method>
-
-      <method name="_getDragTargetTab">
-        <parameter name="event"/>
-        <body><![CDATA[
-
-          if (document.dragTab.localName != "tab")
-            return null;
-
-          let tab = document.dragTab;
-
-          if ((event.type != "drop") && (event.type != "dragover"))
-            return tab;
-
-          let tabRect = tab.getBoundingClientRect();
-          if (event.screenX < tab.screenX + tabRect.width * .25)
-            return null;
-
-          if (event.screenX > tab.screenX + tabRect.width * .75)
-            return null;
-
-          return tab;
-        ]]></body>
-      </method>
-
-
-      <method name="_getDropIndex">
-        <parameter name="event"/>
-        <body><![CDATA[
-          let tabs = this.childNodes;
-
-          if (window.getComputedStyle(this).direction == "ltr") {
-            for (let i = 0; i < tabs.length; i++)
-              if (event.screenX < (tabs[i].screenX + (tabs[i].getBoundingClientRect().width / 2)))
-                return i;
-          } else {
-            for (let i = 0; i < tabs.length; i++)
-              if (event.screenX > (tabs[i].screenX + (tabs[i].getBoundingClientRect().width / 2)))
-                return i;
-          }
-
-           return tabs.length;
-        ]]></body>
-      </method>
-    </implementation>
-    <handlers>
-      <handler event="select"><![CDATA[
-          this._handleTabSelect();
-
-          if (!("updateCurrentTab" in this.tabmail) ||
-              event.target.localName != "tabs")
-            return;
-
-          this.tabmail.updateCurrentTab();
-      ]]></handler>
-      <handler event="TabSelect" action="this._handleTabSelect();"/>
-      <handler event="dragstart"><![CDATA[
-        let draggedTab = this._getDragTargetTab(event);
-
-        if (!draggedTab)
-          return;
-
-        let tab = this.tabmail.selectedTab;
-
-        if (!tab || !tab.canClose)
-          return;
-
-
-        let dt = event.dataTransfer;
-
-        // If we drag within the same window, we use the tab directly
-        dt.mozSetDataAt("application/x-moz-tabmail-tab", draggedTab, 0);
-
-        // otherwise we use session restore & JSON to migrate the tab.
-        let uri = this.tabmail.persistTab(tab);
-
-        // In case the tab implements session restore, we use JSON to convert
-        // it into a string
-        //
-        // If a tab does not support session restore it returns null. We can't
-        // moved such tabs to a new window. However moving them within the same
-        // window works perfectly fine
-
-        if (uri)
-          uri = JSON.stringify(uri);
-
-        dt.mozSetDataAt("application/x-moz-tabmail-json", uri, 0);
-
-        dt.mozCursor = "default";
-
-        // Create Drag Image
-        let panel = document.getElementById("tabpanelcontainer");
-
-        let thumbnail = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
-        thumbnail.width = Math.ceil(screen.availWidth / 5.75);
-        thumbnail.height = Math.round(thumbnail.width * 0.5625);
-
-        let snippetWidth = panel.getBoundingClientRect().width * .6;
-        let scale = thumbnail.width / snippetWidth;
-
-        let ctx = thumbnail.getContext("2d");
-
-        ctx.scale(scale, scale);
-
-        ctx.drawWindow(window,
-                panel.screenX - window.mozInnerScreenX,
-                panel.screenY - window.mozInnerScreenY,
-                snippetWidth,
-                snippetWidth * 0.5625,
-                "rgb(255,255,255)");
-
-        dt = event.dataTransfer;
-        dt.setDragImage(thumbnail, 0, 0);
-
-        event.stopPropagation();
-      ]]></handler>
-      <handler event="dragover"><![CDATA[
-        let dt = event.dataTransfer;
-
-        if (dt.mozItemCount == 0)
-          return;
-
-        if (dt.mozGetDataAt("text/toolbarwrapper-id/messengerWindow", 0)
-            != null) {
-          event.preventDefault();
-          event.stopPropagation();
-
-          // Dispatch event to the toolbar
-          let evt = document.createEvent("DragEvent");
-          evt.initDragEvent("dragover", true, true, window, 0, 0, 0, 0, 0,
-                            false, false, false, false, 0, null,
-                            event.dataTransfer);
-
-          if (this.mToolbar.firstChild)
-            this.mToolbar.firstChild.dispatchEvent(evt);
-          else
-            this.mToolbar.dispatchEvent(evt);
-
-          return;
-        }
-
-        // Bug 516247:
-        // in case the user is dragging something else than a tab, and
-        // keeps hovering over a tab, we assume he wants to switch to this tab.
-        if ((dt.mozTypesAt(0)[0] != "application/x-moz-tabmail-tab")
-            && (dt.mozTypesAt(0)[1] != "application/x-moz-tabmail-json")) {
-          let tab = this._getDragTargetTab(event);
-
-          if (!tab)
-            return;
-
-          event.preventDefault();
-          event.stopPropagation();
-
-          if (!this._dragTime) {
-            this._dragTime = Date.now();
-            return;
-          }
-
-          if (Date.now() <= this._dragTime + this._dragOverDelay)
-            return;
-
-          if (this.tabmail.tabContainer.selectedItem == tab)
-            return;
-
-          this.tabmail.tabContainer.selectedItem = tab;
-
-          return;
-        }
-
-        // as some tabs do not support session restore they can't be
-        // moved to a different or new window. We should not show
-        // a dropmarker in such a case
-        if (!dt.mozGetDataAt("application/x-moz-tabmail-json", 0)) {
-          let draggedTab = dt.mozGetDataAt("application/x-moz-tabmail-tab", 0);
-
-          if (!draggedTab)
-            return;
-
-          if (this.tabmail.tabContainer.getIndexOfItem(draggedTab) == -1)
-            return;
-        }
-
-        dt.effectAllowed = "copyMove";
-
-        event.preventDefault();
-        event.stopPropagation();
-
-        let ltr = (window.getComputedStyle(this).direction == "ltr");
-        let ind = this._tabDropIndicator;
-
-        // Let's scroll
-        if (this.hasAttribute("overflow")) {
-          let target = event.originalTarget.getAttribute("anonid");
-
-          let pixelsToScroll = 0;
-
-          if (target == "scrollbutton-up")
-            pixelsToScroll = this.arrowScrollbox.scrollIncrement;
-
-          if (target == "scrollbutton-down")
-            pixelsToScroll = this.arrowScrollbox.scrollIncrement * -1;
-
-          if (ltr)
-            pixelsToScroll = pixelsToScroll * -1;
-
-          if (pixelsToScroll) {
-            // Hide Indicator while Scrolling
-            ind.collapsed = true;
-            this.arrowScrollbox.scrollByPixels(pixelsToScroll);
-            return;
-          }
-        }
-
-        let newIndex = this._getDropIndex(event);
-
-        // fix the DropIndex in case it points to tab that can't be closed
-        let tabInfo = this.tabmail.tabInfo;
-
-        while ((newIndex < tabInfo.length) && !(tabInfo[newIndex].canClose))
-          newIndex++;
-
-
-        var scrollRect = this.arrowScrollbox.scrollClientRect;
-        var rect = this.getBoundingClientRect();
-        var minMargin = scrollRect.left - rect.left;
-        var maxMargin = Math.min(minMargin + scrollRect.width, scrollRect.right);
-
-        if (!ltr)
-          [minMargin, maxMargin] = [this.clientWidth - maxMargin, this.clientWidth - minMargin];
-
-        var newMargin;
-
-        if (newIndex == this.childNodes.length) {
-          let tabRect = this.childNodes[newIndex - 1].getBoundingClientRect();
-
-          if (ltr)
-            newMargin = tabRect.right - rect.left;
-          else
-            newMargin = rect.right - tabRect.left;
-        } else {
-          let tabRect = this.childNodes[newIndex].getBoundingClientRect();
-
-          if (ltr)
-            newMargin = tabRect.left - rect.left;
-          else
-            newMargin = rect.right - tabRect.right;
-        }
-
-        ind.collapsed = false;
-
-        newMargin += ind.clientWidth / 2;
-        if (!ltr)
-          newMargin *= -1;
-
-        ind.style.transform = "translate(" + Math.round(newMargin) + "px)";
-        ind.style.marginInlineStart = (-ind.clientWidth) + "px";
-      ]]></handler>
-      <handler event="drop"><![CDATA[
-        let dt = event.dataTransfer;
-
-        if (dt.mozItemCount != 1)
-          return;
-
-
-        /* If we're dragging a toolbar button, let's prepend the tabs toolbar
-         * with that button, and then bail out.
-         */
-        let buttonId = dt.mozGetDataAt("text/toolbarwrapper-id/messengerWindow", 0);
-
-        if (buttonId != null) {
-          event.preventDefault();
-          event.stopPropagation();
-
-          let evt = document.createEvent("DragEvent");
-          evt.initDragEvent("drop", true, true, window, 0, 0, 0, 0, 0,
-                            false, false, false, false, 0, null,
-                            event.dataTransfer);
-
-          if (this.mToolbar.firstChild)
-            this.mToolbar.firstChild.dispatchEvent(evt);
-          else
-            this.mToolbar.dispatchEvent(evt);
-
-          return;
-        }
-
-
-        let draggedTab = dt.mozGetDataAt("application/x-moz-tabmail-tab", 0);
-
-        if (!draggedTab)
-          return;
-
-        event.stopPropagation();
-        this._tabDropIndicator.collapsed = true;
-
-        // Is the tab one of our children?
-        if (this.tabmail.tabContainer.getIndexOfItem(draggedTab) == -1) {
-          // It's a tab from an other window, so we have to trigger session
-          // restore to get our tab
-
-          let tabmail2 = draggedTab.ownerDocument.getElementById("tabmail");
-          if (!tabmail2)
-            return;
-
-          let draggedJson = dt.mozGetDataAt("application/x-moz-tabmail-json", 0);
-          if (!draggedJson)
-            return;
-
-          draggedJson = JSON.parse(draggedJson);
-
-          // Some tab exist only once, so we have to gamble a bit. We close
-          // the tab and try to reopen it. If something fails the tab is gone.
-
-          tabmail2.closeTab(draggedTab, true);
-
-          if (!this.tabmail.restoreTab(draggedJson))
-            return;
-
-          draggedTab = this.tabmail.tabContainer.childNodes[
-            this.tabmail.tabContainer.childNodes.length - 1];
-        }
-
-        let idx = this._getDropIndex(event);
-
-        // fix the DropIndex in case it points to tab that can't be closed
-        let tabInfo = this.tabmail.tabInfo;
-        while ((idx < tabInfo.length) && !(tabInfo[idx].canClose))
-          idx++;
-
-        this.tabmail.moveTabTo(draggedTab, idx);
-
-        this.tabmail.switchToTab(draggedTab);
-        this.tabmail.updateCurrentTab();
-      ]]></handler>
-
-      <handler event="dragend"><![CDATA[
-
-        // Note: while this case is correctly handled here, this event
-        // isn't dispatched when the tab is moved within the tabstrip,
-        // see bug 460801.
-
-        // the user pressed ESC to cancel the drag, or the drag succeeded
-        var dt = event.dataTransfer;
-        if ((dt.mozUserCancelled) || (dt.dropEffect != "none"))
-          return;
-
-        // Disable detach within the browser toolbox
-        var eX = event.screenX;
-        var wX = window.screenX;
-
-        // check if the drop point is horizontally within the window
-        if (eX > wX && eX < (wX + window.outerWidth)) {
-          let bo = this.arrowScrollbox;
-          // also avoid detaching if the the tab was dropped too close to
-          // the tabbar (half a tab)
-          let endScreenY = bo.screenY + 1.5 * bo.getBoundingClientRect().height;
-          let eY = event.screenY;
-
-          if (eY < endScreenY && eY > window.screenY)
-            return;
-        }
-
-        // user wants to deatach tab from window...
-        if (dt.mozItemCount != 1)
-          return;
-
-        let draggedTab = dt.mozGetDataAt("application/x-moz-tabmail-tab", 0);
-
-        if (!draggedTab)
-          return;
-
-        this.tabmail.replaceTabWithWindow(draggedTab);
-
-      ]]></handler>
-
-      <handler event="dragexit"><![CDATA[
-        this._dragTime = 0;
-
-        this._tabDropIndicator.collapsed = true;
-        event.stopPropagation();
-
-      ]]></handler>
-      <handler event="click" button="0"><![CDATA[
-        if (!event.originalTarget.classList.contains("tabs-closebutton")) {
-          return;
-        }
-
-        let tabbedBrowser = document.getElementById("tabmail");
-        if (this.localName == "tab") {
-          /* The only sequence in which a second click event (i.e. dblclik)
-           * can be dispatched on an in-tab close button is when it is shown
-           * after the first click (i.e. the first click event was dispatched
-           * on the tab). This happens when we show the close button only on
-           * the active tab. (bug 352021)
-           * The only sequence in which a third click event can be dispatched
-           * on an in-tab close button is when the tab was opened with a
-           * double click on the tabbar. (bug 378344)
-           * In both cases, it is most likely that the close button area has
-           * been accidentally clicked, therefore we do not close the tab.
-           */
-          if (event.detail > 1)
-            return;
-
-          tabbedBrowser.removeTabByNode(this);
-          tabbedBrowser._blockDblClick = true;
-          let tabContainer = tabbedBrowser.tabContainer;
-
-          /* XXXmano hack (see bug 343628):
-           * Since we're removing the event target, if the user
-           * double-clicks this button, the dblclick event will be dispatched
-           * with the tabbar as its event target (and explicit/originalTarget),
-           * which treats that as a mouse gesture for opening a new tab.
-           * In this context, we're manually blocking the dblclick event
-           * (see onTabBarDblClick).
-           */
-          let clickedOnce = false;
-          let enableDblClick = function enableDblClick(event) {
-            let target = event.originalTarget;
-            if (target.className == "tab-close-button") {
-              target._ignoredClick = true;
-            }
-            if (!clickedOnce) {
-              clickedOnce = true;
-              return;
-            }
-            tabContainer._blockDblClick = false;
-            tabContainer.removeEventListener("click", enableDblClick, true);
-          };
-          tabContainer.addEventListener("click", enableDblClick, true);
-        } else { // "tabs"
-          tabbedBrowser.removeCurrentTab();
-        }
-      ]]></handler>
-      <handler event="dblclick" button="0" phase="capturing"><![CDATA[
-        // for the one-close-button case
-        event.stopPropagation();
-      ]]></handler>
-    </handlers>
-  </binding>
-
   <binding id="tabmail-alltabs-popup"
            extends="chrome://global/content/bindings/popup.xml#popup">
     <implementation>
       <field name="_xulWindow">
         null
       </field>
       <field name="_mutationObserver">
         null
@@ -2778,17 +1945,17 @@
     </implementation>
 
     <handlers>
       <handler event="popupshowing">
       <![CDATA[
         // set up the menu popup
         let tabmail = document.getElementById("tabmail");
         var tabcontainer = tabmail.tabContainer;
-        var tabs = tabcontainer.childNodes;
+        var tabs = tabcontainer.allTabs;
 
         // Listen for changes in the tab bar.
         this._mutationObserver.observe(tabcontainer, {
           attributes: true,
           subtree: true,
           attributeFilter: ["label", "crop", "busy", "image", "selected"],
         });
 
--- a/mail/base/jar.mn
+++ b/mail/base/jar.mn
@@ -68,16 +68,17 @@ messenger.jar:
     content/messenger/phishingDetector.js           (content/phishingDetector.js)
     content/messenger/mail-offline.js               (content/mail-offline.js)
     content/messenger/aboutDialog.css               (content/aboutDialog.css)
     content/messenger/converterDialog.css           (content/converterDialog.css)
     content/messenger/notification.css              (content/notification.css)
 *   content/messenger/messenger.css                 (content/messenger.css)
     content/messenger/search.xml                    (content/search.xml)
     content/messenger/tabmail.xml                   (content/tabmail.xml)
+    content/messenger/tabmail-tabs.js               (content/tabmail-tabs.js)
     content/messenger/tabbrowser-tab.js             (content/tabbrowser-tab.js)
     content/messenger/tabmail.css                   (content/tabmail.css)
     content/messenger/statuspanel.js                (content/statuspanel.js)
     content/messenger/newTagDialog.xul              (content/newTagDialog.xul)
     content/messenger/newTagDialog.js               (content/newTagDialog.js)
     content/messenger/composerOverlay.css           (content/composerOverlay.css)
     content/messenger/threadPane.js                 (content/threadPane.js)
     content/messenger/protovis-r2.6-modded.js       (content/protovis-r2.6-modded.js)
--- a/mail/components/extensions/test/browser/browser_ext_menus.js
+++ b/mail/components/extensions/test/browser/browser_ext_menus.js
@@ -135,17 +135,17 @@ add_task(async function test_tab() {
   let extension = createExtension();
   await extension.startup();
 
   let tabmail = document.getElementById("tabmail");
   window.openContentTab("about:config");
   window.openContentTab("about:mozilla");
   tabmail.openTab("folder", { folder: gFolders[0] });
 
-  let tabs = tabmail.tabbox.tabs.children;
+  let tabs = document.getElementById("tabmail-tabs").allTabs;
   let menu = document.getElementById("tabContextMenu");
 
   await checkTabEvent(0, false, true);
   await checkTabEvent(1, false, false);
   await checkTabEvent(2, false, false);
   await checkTabEvent(3, true, true);
 
   await extension.unload();
--- a/mail/test/mozmill/content-policy/test-compose-mailto.js
+++ b/mail/test/mozmill/content-policy/test-compose-mailto.js
@@ -44,17 +44,17 @@ function setupModule(module) {
   let cth = collector.getModule("content-tab-helpers");
   cth.installInto(module);
 }
 
 function test_openComposeFromMailToLink() {
   // Open a content tab with the mailto link in it.
     // To open a tab we're going to have to cheat and use tabmail so we can load
   // in the data of what we want.
-  gPreCount = mc.tabmail.tabContainer.childNodes.length;
+  gPreCount = mc.tabmail.tabContainer.allTabs.length;
   gNewTab = open_content_tab_with_url(url + "mailtolink.html");
   gComposeWin = composeHelper.open_compose_with_element_click("mailtolink");
 }
 
 function test_checkInsertImage() {
   // First focus on the editor element
   gComposeWin.e("content-frame").focus();
 
@@ -93,12 +93,12 @@ function test_checkInsertImage() {
     throw new Error("Loading of image has been unexpectedly blocked in a mailto compose window");
 }
 
 function test_closeComposeWindowAndTab() {
   composeHelper.close_compose_window(gComposeWin);
 
   mc.tabmail.closeTab(gNewTab);
 
-  if (mc.tabmail.tabContainer.childNodes.length != gPreCount)
+  if (mc.tabmail.tabContainer.allTabs.length != gPreCount)
     throw new Error("The content tab didn't close");
 }
 
--- a/mail/test/mozmill/content-policy/test-dns-prefetch.js
+++ b/mail/test/mozmill/content-policy/test-dns-prefetch.js
@@ -154,23 +154,23 @@ function test_dnsPrefetch_compose() {
   checkComposeWindow(0);
   checkComposeWindow(1);
   checkComposeWindow(2);
 }
 
 function test_dnsPrefetch_contentTab() {
   // To open a tab we're going to have to cheat and use tabmail so we can load
   // in the data of what we want.
-  let preCount = mc.tabmail.tabContainer.childNodes.length;
+  let preCount = mc.tabmail.tabContainer.allTabs.length;
 
   let dataurl = "data:text/html,<html><head><title>test dns prefetch</title>" +
     "</head><body>test dns prefetch</body></html>";
 
   let newTab = open_content_tab_with_url(dataurl);
 
   if (!mc.tabmail.getBrowserForSelectedTab().docShell.allowDNSPrefetch)
     throw new Error("DNS prefetch unexpectedly disabled in content tabs");
 
   mc.tabmail.closeTab(newTab);
 
-  if (mc.tabmail.tabContainer.childNodes.length != preCount)
+  if (mc.tabmail.tabContainer.allTabs.length != preCount)
     throw new Error("The content tab didn't close");
 }
--- a/mail/test/mozmill/content-policy/test-exposed-in-content-tabs.js
+++ b/mail/test/mozmill/content-policy/test-exposed-in-content-tabs.js
@@ -102,30 +102,30 @@ function addMsgToFolder(folder) {
   // This is the full url to the message that we want (i.e. passing this to
   // a browser element or iframe will display it).
   return neckoURL.value.spec;
 }
 
 function checkContentTab(msgURL) {
   // To open a tab we're going to have to cheat and use tabmail so we can load
   // in the data of what we want.
-  let preCount = mc.tabmail.tabContainer.childNodes.length;
+  let preCount = mc.tabmail.tabContainer.allTabs.length;
 
   let dataurl = "data:text/html,<html><head><title>test exposed</title>" +
     '</head><body><iframe id="msgIframe" src="' + msgURL + '"/></body></html>';
 
   let newTab = open_content_tab_with_url(dataurl);
 
   if (mc.window.content.document.getElementById("msgIframe")
         .contentDocument.URL != "about:blank")
     throw new Error("Message display/access has not been blocked from remote content!");
 
   mc.tabmail.closeTab(newTab);
 
-  if (mc.tabmail.tabContainer.childNodes.length != preCount)
+  if (mc.tabmail.tabContainer.allTabs.length != preCount)
     throw new Error("The content tab didn't close");
 }
 
 function test_exposedInContentTabs() {
   be_in_folder(folder);
 
   assert_nothing_selected();
 
--- a/mail/test/mozmill/content-policy/test-general-content-policy.js
+++ b/mail/test/mozmill/content-policy/test-general-content-policy.js
@@ -260,27 +260,27 @@ function allowRemoteContentAndCheck(test
   if (!test.checkForAllowed(mc.window.content.document
                               .getElementById("testelement")))
     throw new Error(test.type + " has been unexpectedly blocked in message content");
 }
 
 function checkContentTab(test) {
   // To open a tab we're going to have to cheat and use tabmail so we can load
   // in the data of what we want.
-  let preCount = mc.tabmail.tabContainer.childNodes.length;
+  let preCount = mc.tabmail.tabContainer.allTabs.length;
 
   let newTab = open_content_tab_with_url(url + test.webPage);
 
   if (!test.checkForAllowed(mc.window.content.document
                               .getElementById("testelement")))
     throw new Error(test.type + " has been unexpectedly blocked in content tab");
 
   mc.tabmail.closeTab(newTab);
 
-  if (mc.tabmail.tabContainer.childNodes.length != preCount)
+  if (mc.tabmail.tabContainer.allTabs.length != preCount)
     throw new Error("The content tab didn't close");
 }
 
 /**
  * Check remote content is not blocked in feed message (flagged with
  * nsMsgMessageFlags::FeedMsg)
  */
 function checkAllowFeedMsg(test) {
--- a/mail/test/mozmill/content-policy/test-plugins-policy.js
+++ b/mail/test/mozmill/content-policy/test-plugins-policy.js
@@ -182,20 +182,20 @@ function test_3paneWindowDeniedAgain() {
 
 function test_checkStandaloneMessageWindowDenied() {
   checkStandaloneMessageWindow(false);
 }
 
 function test_checkContentTab() {
   // To open a tab we're going to have to cheat and use tabmail so we can load
   // in the data of what we want.
-  let preCount = mc.tabmail.tabContainer.childNodes.length;
+  let preCount = mc.tabmail.tabContainer.allTabs.length;
 
   let newTab = open_content_tab_with_url(url + "plugin.html");
 
   if (isPluginLoaded(mc.tabmail.getBrowserForSelectedTab().contentDocument))
     throw new Error("Plugin has been unexpectedly not blocked in content tab");
 
   mc.tabmail.closeTab(newTab);
 
-  if (mc.tabmail.tabContainer.childNodes.length != preCount)
+  if (mc.tabmail.tabContainer.allTabs.length != preCount)
     throw new Error("The content tab didn't close");
 }
--- a/mail/test/mozmill/content-tabs/test-content-tab.js
+++ b/mail/test/mozmill/content-tabs/test-content-tab.js
@@ -120,23 +120,23 @@ function test_content_tab_context_menu()
   assert_not_equals(mailContext.firstChild.label, "Click me!");
   assert_element_not_visible("page-menu-separator");
   close_popup(mc, new elementslib.Elem(mailContext));
 }
 
 /*
  // We don't have an UI to test openin content tabs twice anymore.
 function test_content_tab_open_same() {
-  let preCount = mc.tabmail.tabContainer.childNodes.length;
+  let preCount = mc.tabmail.tabContainer.allTabs.length;
 
   mc.click(new elementslib.Elem(mc.menus.helpMenu.whatsNew));
 
   controller.sleep(0);
 
-  if (mc.tabmail.tabContainer.childNodes.length != preCount)
+  if (mc.tabmail.tabContainer.allTabs.length != preCount)
     throw new Error("A new content tab was opened when it shouldn't have been");
 
   // Double-check browser is still the same.
   if (mc.window.content.location != whatsUrl)
     throw new Error("window.content is not set to the url loaded, incorrect type=\"...\"?");
 }
 */
 
@@ -146,17 +146,17 @@ function test_content_tab_default_favico
 
   assert_tab_has_title(tab, "What's New Content Test 1");
   // Check the location of the favicon, this should be the site favicon in this
   // test.
   assert_content_tab_has_favicon(tab, url + "favicon.ico");
 }
 
 function test_content_tab_onbeforeunload() {
-  let count = mc.tabmail.tabContainer.childNodes.length;
+  let count = mc.tabmail.tabContainer.allTabs.length;
   let tab = mc.tabmail.tabInfo[count - 1];
   tab.browser.contentWindow.addEventListener("beforeunload", function(event) {
     event.returnValue = "Green llama in your car";
   });
 
   const interactionPref = "dom.require_user_interaction_for_beforeunload";
   Services.prefs.setBoolPref(interactionPref, false);
 
--- a/mail/test/mozmill/downloads/test-about-downloads.js
+++ b/mail/test/mozmill/downloads/test-about-downloads.js
@@ -119,20 +119,20 @@ function setupModule(module) {
   downloadsTab = open_about_downloads();
 }
 
 function setupTest(test) {
   downloadsView.init();
 }
 
 function open_about_downloads() {
-  let preCount = mc.tabmail.tabContainer.childNodes.length;
+  let preCount = mc.tabmail.tabContainer.allTabs.length;
   let newTab = mc.tabmail.openTab("chromeTab", { chromePage: "about:downloads",
                                                  clickHandler: "specialTabs.aboutClickHandler(event);" });
-  mc.waitFor(() => mc.tabmail.tabContainer.childNodes.length == preCount + 1,
+  mc.waitFor(() => mc.tabmail.tabContainer.allTabs.length == preCount + 1,
              "Timeout waiting for about:downloads tab");
 
   wait_for_browser_load(newTab.browser, "about:downloads");
   // We append new tabs at the end, so check the last one.
   let expectedNewTab = mc.tabmail.tabInfo[preCount];
   return expectedNewTab;
 }
 
--- a/mail/test/mozmill/folder-display/test-displaying-messages-in-folder-tabs.js
+++ b/mail/test/mozmill/folder-display/test-displaying-messages-in-folder-tabs.js
@@ -141,31 +141,31 @@ function _verify_display_in_existing_tab
   assert_selected_and_displayed(aMsgHdr);
 }
 
 /**
  * Test displaying a message with the same folder already open.
  */
 function test_display_message_in_same_folder() {
   be_in_folder(folderA);
-  let preCount = mc.tabmail.tabContainer.childNodes.length;
+  let preCount = mc.tabmail.tabContainer.allTabs.length;
 
   let msgHdr = msgHdrsInFolderA[indexes.next().value];
 
   display_message_in_folder_tab(msgHdr);
   // Verify
   _verify_display_in_existing_tab(preCount, folderA, msgHdr);
 }
 
 /**
  * Test displaying a message with a different folder open.
  */
 function test_display_message_in_different_folder() {
   be_in_folder(folderB);
-  let preCount = mc.tabmail.tabContainer.childNodes.length;
+  let preCount = mc.tabmail.tabContainer.allTabs.length;
 
   let msgHdr = msgHdrsInFolderA[indexes.next().value];
 
   display_message_in_folder_tab(msgHdr);
   // Verify
   _verify_display_in_existing_tab(preCount, folderA, msgHdr);
 }
 
@@ -174,17 +174,17 @@ function test_display_message_in_differe
  */
 function test_display_message_in_same_folder_with_message_tab_active() {
   be_in_folder(folderA);
 
   let indexToOpen = indexes.next().value;
   select_click_row(indexToOpen);
   let messageTab = open_selected_message_in_new_tab(false);
 
-  let preCount = mc.tabmail.tabContainer.childNodes.length;
+  let preCount = mc.tabmail.tabContainer.allTabs.length;
   let msgHdr = msgHdrsInFolderA[indexes.next().value];
   display_message_in_folder_tab(msgHdr);
   // Verify
   _verify_display_in_existing_tab(preCount, folderA, msgHdr);
 
   // Clean up, close the message tab
   close_tab(messageTab);
 }
@@ -195,17 +195,17 @@ function test_display_message_in_same_fo
  */
 function test_display_message_in_different_folder_with_message_tab_active() {
   be_in_folder(folderA);
 
   let indexToOpen = indexes.next().value;
   select_click_row(indexToOpen);
   let messageTab = open_selected_message_in_new_tab(false);
 
-  let preCount = mc.tabmail.tabContainer.childNodes.length;
+  let preCount = mc.tabmail.tabContainer.allTabs.length;
   let msgHdr = msgHdrsInFolderB[indexes.next().value];
   display_message_in_folder_tab(msgHdr);
   // Verify
   _verify_display_in_existing_tab(preCount, folderB, msgHdr);
 
   // Clean up, close the message tab
   close_tab(messageTab);
 }
@@ -214,17 +214,17 @@ function test_display_message_in_differe
  * Test displaying a message with the same folder open but filtered.
  */
 function test_display_message_in_same_folder_filtered() {
   be_in_folder(folderA);
   set_mail_view(MailViewConstants.kViewItemTags, "$label1");
   // Make sure all the messages have actually disappeared
   assert_messages_not_in_view(msgHdrsInFolderA);
 
-  let preCount = mc.tabmail.tabContainer.childNodes.length;
+  let preCount = mc.tabmail.tabContainer.allTabs.length;
   let msgHdr = msgHdrsInFolderA[indexes.next().value];
   display_message_in_folder_tab(msgHdr);
   // Verify
   _verify_display_in_existing_tab(preCount, folderA, msgHdr);
 
   // Reset the mail view
   set_mail_view(MailViewConstants.kViewItemAll, null);
 }
@@ -238,17 +238,17 @@ function test_display_message_in_differe
   // Make sure all the messages have actually disappeared
   assert_messages_not_in_view(msgHdrsInFolderB);
 
   // Do the same for folder A
   be_in_folder(folderA);
   set_mail_view(MailViewConstants.kViewItemTags, "$label2");
   assert_messages_not_in_view(msgHdrsInFolderA);
 
-  let preCount = mc.tabmail.tabContainer.childNodes.length;
+  let preCount = mc.tabmail.tabContainer.allTabs.length;
   let msgHdr = msgHdrsInFolderB[indexes.next().value];
   display_message_in_folder_tab(msgHdr);
   // Verify
   _verify_display_in_existing_tab(preCount, folderB, msgHdr);
   // Reset folder B's state
   be_in_folder(folderB);
   set_mail_view(MailViewConstants.kViewItemAll, null);
 
@@ -270,17 +270,17 @@ function test_display_message_in_same_fo
   let messageTab = open_selected_message_in_new_tab(false);
 
   switch_tab(folderTab);
   set_mail_view(MailViewConstants.kViewItemTags, "$label1");
   // Make sure all the messages have actually disappeared
   assert_messages_not_in_view(msgHdrsInFolderB);
   switch_tab(messageTab);
 
-  let preCount = mc.tabmail.tabContainer.childNodes.length;
+  let preCount = mc.tabmail.tabContainer.allTabs.length;
   let msgHdr = msgHdrsInFolderB[indexes.next().value];
   display_message_in_folder_tab(msgHdr);
   // Verify
   _verify_display_in_existing_tab(preCount, folderB, msgHdr);
 
   // Clean up, close the message tab and reset the mail view
   close_tab(messageTab);
   set_mail_view(MailViewConstants.kViewItemAll, null);
@@ -298,17 +298,17 @@ function
   assert_messages_not_in_view(msgHdrsInFolderA);
 
   be_in_folder(folderB);
   set_mail_view(MailViewConstants.kViewItemTags, "$label3");
   // There should be one message in here
   select_click_row(0);
   let messageTab = open_selected_message_in_new_tab(false);
 
-  let preCount = mc.tabmail.tabContainer.childNodes.length;
+  let preCount = mc.tabmail.tabContainer.allTabs.length;
   let msgHdr = msgHdrsInFolderA[indexes.next().value];
   display_message_in_folder_tab(msgHdr);
   // Verify
   _verify_display_in_existing_tab(preCount, folderA, msgHdr);
   // Close the message tab
   close_tab(messageTab);
   // Reset folder A's state
   be_in_folder(folderA);
@@ -325,17 +325,17 @@ function
  * Test displaying a message with the folder set to show unread messages only.
  */
 function test_display_message_in_same_folder_unread() {
   be_in_folder(folderB);
   set_show_unread_only(true);
   // Make sure all the messages have actually disappeared
   assert_messages_not_in_view(msgHdrsInFolderB);
 
-  let preCount = mc.tabmail.tabContainer.childNodes.length;
+  let preCount = mc.tabmail.tabContainer.allTabs.length;
   let msgHdr = msgHdrsInFolderB[indexes.next().value];
   display_message_in_folder_tab(msgHdr);
   // Verify
   _verify_display_in_existing_tab(preCount, folderB, msgHdr);
   // Reset the state
   be_in_folder(folderB);
   set_show_unread_only(false);
 }
@@ -350,17 +350,17 @@ function test_display_message_in_differe
   // Make sure all the messages have actually disappeared
   assert_messages_not_in_view(msgHdrsInFolderA);
 
   // Do the same for folder B
   be_in_folder(folderB);
   set_show_unread_only(true);
   assert_messages_not_in_view(msgHdrsInFolderB);
 
-  let preCount = mc.tabmail.tabContainer.childNodes.length;
+  let preCount = mc.tabmail.tabContainer.allTabs.length;
   let msgHdr = msgHdrsInFolderA[indexes.next().value];
   display_message_in_folder_tab(msgHdr);
   // Verify
   _verify_display_in_existing_tab(preCount, folderA, msgHdr);
   // Reset folder A's state
   be_in_folder(folderA);
   set_show_unread_only(false);
 
@@ -374,17 +374,17 @@ function test_display_message_in_differe
 /**
  * Test that we correctly scroll to the index of the message in a folder.
  */
 function test_display_message_scrolls_to_message() {
   be_in_folder(folderC);
   // Scroll to the top
   mc.folderDisplay.ensureRowIsVisible(0);
 
-  let preCount = mc.tabmail.tabContainer.childNodes.length;
+  let preCount = mc.tabmail.tabContainer.allTabs.length;
   let msgHdr = mc.dbView.getMsgHdrAt(45);
   display_message_in_folder_tab(msgHdr);
 
   // Check that row 45 is visible
   assert_row_visible(45);
   // Verify the rest
   _verify_display_in_existing_tab(preCount, folderC, msgHdr);
 }
--- a/mail/test/mozmill/folder-display/test-opening-messages-without-a-backing-view.js
+++ b/mail/test/mozmill/folder-display/test-opening-messages-without-a-backing-view.js
@@ -41,17 +41,17 @@ function setupModule(module) {
 }
 
 /**
  * Test opening a single message without a backing view in a new tab.
  */
 function test_open_single_message_without_backing_view_in_tab() {
   set_open_message_behavior("NEW_TAB");
   let folderTab = mc.tabmail.currentTabInfo;
-  let preCount = mc.tabmail.tabContainer.childNodes.length;
+  let preCount = mc.tabmail.tabContainer.allTabs.length;
   be_in_folder(folder);
 
   if (!msgHdrsInFolder) {
     msgHdrsInFolder = [];
     // Make a list of all the message headers in this folder
     for (let i = 0; i < 10; i++)
       msgHdrsInFolder.push(mc.dbView.getMsgHdrAt(i));
   }
@@ -79,17 +79,17 @@ function test_open_single_message_withou
 }
 
 /**
  * Test opening multiple messages without backing views in new tabs.
  */
 function test_open_multiple_messages_without_backing_views_in_tabs() {
   set_open_message_behavior("NEW_TAB");
   let folderTab = mc.tabmail.currentTabInfo;
-  let preCount = mc.tabmail.tabContainer.childNodes.length;
+  let preCount = mc.tabmail.tabContainer.allTabs.length;
   be_in_folder(folder);
 
   // Get a reference to a bunch of headers
   let msgHdrs = msgHdrsInFolder.slice(0, NUM_MESSAGES_TO_OPEN);
 
   // Open them
   MailUtils.displayMessages(msgHdrs);
   // This is going to trigger a message display in the main 3pane window. Since
--- a/mail/test/mozmill/folder-display/test-opening-messages.js
+++ b/mail/test/mozmill/folder-display/test-opening-messages.js
@@ -42,17 +42,17 @@ function setupModule(module) {
 }
 
 /**
  * Test opening a single message in a new tab.
  */
 function test_open_single_message_in_tab() {
   set_open_message_behavior("NEW_TAB");
   let folderTab = mc.tabmail.currentTabInfo;
-  let preCount = mc.tabmail.tabContainer.childNodes.length;
+  let preCount = mc.tabmail.tabContainer.allTabs.length;
   be_in_folder(folder);
   // Select one message
   let msgHdr = select_click_row(1);
   // Open it
   open_selected_message();
   // This is going to trigger a message display in the main 3pane window
   wait_for_message_display_completion(mc);
   // Check that the tab count has increased by 1
@@ -73,17 +73,17 @@ function test_open_single_message_in_tab
 }
 
 /**
  * Test opening multiple messages in new tabs.
  */
 function test_open_multiple_messages_in_tabs() {
   set_open_message_behavior("NEW_TAB");
   let folderTab = mc.tabmail.currentTabInfo;
-  let preCount = mc.tabmail.tabContainer.childNodes.length;
+  let preCount = mc.tabmail.tabContainer.allTabs.length;
   be_in_folder(folder);
 
   // Select a bunch of messages
   select_click_row(1);
   let selectedMessages = select_shift_click_row(NUM_MESSAGES_TO_OPEN);
   // Open them
   open_selected_messages();
   // This is going to trigger a message display in the main 3pane window
--- a/mail/test/mozmill/im/test-chat-tab-restore.js
+++ b/mail/test/mozmill/im/test-chat-tab-restore.js
@@ -11,23 +11,23 @@ var RELATIVE_ROOT = "../shared-modules";
 var MODULE_REQUIRES = ["folder-display-helpers"];
 
 /**
  * Create a new chat tab, making that tab the current tab. We block until the
  * message finishes loading. (Inspired by open_selected_message_in_new_tab)
  */
 function open_chat_tab() {
   // Get the current tab count so we can make sure the tab actually opened.
-  let preCount = mc.tabmail.tabContainer.childNodes.length;
+  let preCount = mc.tabmail.tabContainer.allTabs.length;
 
   mc.tabmail.openTab("chat", {});
   mark_action("imh", "open_chat_tab", []);
   wait_for_chat_tab_to_open(mc);
 
-  if (mc.tabmail.tabContainer.childNodes.length != preCount + 1)
+  if (mc.tabmail.tabContainer.allTabs.length != preCount + 1)
     throw new Error("The tab never actually got opened!");
 
   let newTab = mc.tabmail.tabInfo[preCount];
   return newTab;
 }
 
 function wait_for_chat_tab_to_open(aController) {
   if (aController == null)
@@ -67,16 +67,16 @@ function test_chat_tab_restore() {
       mc.tabmail.closeTab(1);
   };
 
   open_chat_tab();
   let state = mc.tabmail.persistTabs();
   closeTabs();
   mc.tabmail.restoreTabs(state);
 
-  if (mc.tabmail.tabContainer.childNodes.length < 2)
+  if (mc.tabmail.tabContainer.allTabs.length < 2)
     throw new Error("The tab is not restored!");
 
   let tabTypes = ["folder", "chat"];
   for (let i in tabTypes) {
     assert_tab_mode_name(mc.tabmail.tabInfo[i], tabTypes[i]);
   }
 }
--- a/mail/test/mozmill/newmailaccount/test-newmailaccount.js
+++ b/mail/test/mozmill/newmailaccount/test-newmailaccount.js
@@ -992,34 +992,34 @@ function test_external_link_opening_beha
   mc.tabmail.closeTab(tab);
 }
 
 /**
  * Test that if the provider returns XML that we can't turn into an account,
  * then we error out and go back to the Account Provisioner dialog.
  */
 function test_return_to_provisioner_on_error_XML() {
-  const kOriginalTabNum = mc.tabmail.tabContainer.childNodes.length;
+  const kOriginalTabNum = mc.tabmail.tabContainer.allTabs.length;
 
   get_to_order_form("error@error.invalid");
 
   let tab = mc.tabmail.currentTabInfo;
 
   plan_for_modal_dialog("AccountCreation", close_dialog_immediately);
 
   // Click the OK button to order the account.
   let btn = tab.browser.contentWindow.document.querySelector("input[value=Send]");
   mc.click(new elib.Elem(btn));
 
   wait_for_modal_dialog("AccountCreation");
 
   // We should be done executing the function defined in plan_for_modal_dialog
   // now, so the Account Provisioner dialog should be closed, and the order
   // form tab should have been closed.
-  assert_equals(kOriginalTabNum, mc.tabmail.tabContainer.childNodes.length,
+  assert_equals(kOriginalTabNum, mc.tabmail.tabContainer.allTabs.length,
                 "Timed out waiting for the order form tab to close.");
 }
 
 /**
  * Test that if we initiate a search, then the search input, the search button,
  * and all checkboxes should be disabled. The ability to close the window should
  * still be enabled though.
  */
--- a/mail/test/mozmill/search-window/test-search-window.js
+++ b/mail/test/mozmill/search-window/test-search-window.js
@@ -125,17 +125,17 @@ function test_go_search() {
 
 /**
  * Test opening a single search result in a new tab.
  */
 function test_open_single_search_result_in_tab() {
   swc.window.focus();
   set_open_message_behavior("NEW_TAB");
   let folderTab = mc.tabmail.currentTabInfo;
-  let preCount = mc.tabmail.tabContainer.childNodes.length;
+  let preCount = mc.tabmail.tabContainer.allTabs.length;
 
   // Select one message
   swc.e("threadTree").focus();
   let msgHdr = select_click_row(1, swc);
   // Open the selected message
   open_selected_message(swc);
   // This is going to trigger a message display in the main 3pane window
   wait_for_message_display_completion(mc);
@@ -154,17 +154,17 @@ function test_open_single_search_result_
 
 /**
  * Test opening multiple search results in new tabs.
  */
 function test_open_multiple_search_results_in_new_tabs() {
   swc.window.focus();
   set_open_message_behavior("NEW_TAB");
   let folderTab = mc.tabmail.currentTabInfo;
-  let preCount = mc.tabmail.tabContainer.childNodes.length;
+  let preCount = mc.tabmail.tabContainer.allTabs.length;
 
   // Select a bunch of messages
   swc.e("threadTree").focus();
   select_click_row(1, swc);
   let selectedMessages = select_shift_click_row(NUM_MESSAGES_TO_OPEN, swc);
   // Open them
   open_selected_messages(swc);
   // This is going to trigger a message display in the main 3pane window
--- a/mail/test/mozmill/shared-modules/test-content-tab-helpers.js
+++ b/mail/test/mozmill/shared-modules/test-content-tab-helpers.js
@@ -155,24 +155,24 @@ var NotificationWatcher = {
 function open_content_tab_with_url(aURL, aClickHandler, aBackground, aController) {
   if (aClickHandler === undefined)
     aClickHandler = null;
   if (aBackground === undefined)
     aBackground = false;
   if (aController === undefined)
     aController = mc;
 
-  let preCount = mc.tabmail.tabContainer.childNodes.length;
+  let preCount = mc.tabmail.tabContainer.allTabs.length;
   mc.tabmail.openTab("contentTab", {
     contentPage: aURL,
     background: aBackground,
     clickHandler: aClickHandler,
   });
   utils.waitFor(() => (
-                  aController.tabmail.tabContainer.childNodes.length == preCount + 1),
+                aController.tabmail.tabContainer.allTabs.length == preCount + 1),
                 "Timeout waiting for the content tab to open with URL: " + aURL,
                 FAST_TIMEOUT, FAST_INTERVAL);
 
   // We append new tabs at the end, so check the last one.
   let expectedNewTab = aController.tabmail.tabInfo[preCount];
   folderDisplayHelper.assert_selected_tab(expectedNewTab);
   wait_for_content_tab_load(expectedNewTab, aURL);
   return expectedNewTab;
@@ -189,24 +189,24 @@ function open_content_tab_with_url(aURL,
  *                      to |mc|.
  * @param [aTabType]    Optional tab type to expect (string).
  * @returns The newly-opened tab.
  */
 function open_content_tab_with_click(aElem, aExpectedURL, aController, aTabType = "contentTab") {
   if (aController === undefined)
     aController = mc;
 
-  let preCount = aController.tabmail.tabContainer.childNodes.length;
+  let preCount = aController.tabmail.tabContainer.allTabs.length;
   if (typeof(aElem) != "function")
     aController.click(new elib.Elem(aElem));
   else
     aElem();
 
   utils.waitFor(() => (
-                  aController.tabmail.tabContainer.childNodes.length == preCount + 1),
+                aController.tabmail.tabContainer.allTabs.length == preCount + 1),
                 "Timeout waiting for the content tab to open",
                 FAST_TIMEOUT, FAST_INTERVAL);
 
   // We append new tabs at the end, so check the last one.
   let expectedNewTab = aController.tabmail.tabInfo[preCount];
   folderDisplayHelper.assert_selected_tab(expectedNewTab);
   folderDisplayHelper.assert_tab_mode_name(expectedNewTab, aTabType);
   wait_for_content_tab_load(expectedNewTab, aExpectedURL);
--- a/mail/test/mozmill/shared-modules/test-folder-display-helpers.js
+++ b/mail/test/mozmill/shared-modules/test-folder-display-helpers.js
@@ -577,36 +577,36 @@ var open_selected_message = open_selecte
  *                    background. If false or not given, then the tab is opened
  *                    in the foreground.
  *
  * @return The tab info of the new tab (a more persistent identifier for tabs
  *     than the index, which will change as tabs open/close).
  */
 function open_selected_message_in_new_tab(aBackground) {
   // get the current tab count so we can make sure the tab actually opened.
-  let preCount = mc.tabmail.tabContainer.childNodes.length;
+  let preCount = mc.tabmail.tabContainer.allTabs.length;
 
   // save the current tab as the 'other' tab
   otherTab = mc.tabmail.currentTabInfo;
 
   // We won't trigger a new message load if we're in the background.
   if (!aBackground)
     plan_for_message_display(mc);
   mc.tabmail.openTab("message", {msgHdr: mc.folderDisplay.selectedMessage,
       viewWrapperToClone: mc.folderDisplay.view,
       background: aBackground});
   wait_for_message_display_completion(mc, !aBackground);
 
   // check that the tab count increased
-  if (mc.tabmail.tabContainer.childNodes.length != preCount + 1)
+  if (mc.tabmail.tabContainer.allTabs.length != preCount + 1)
     throw new Error("The tab never actually got opened!");
 
   // We append new tabs at the end, so return the last tab
   let newTab =
-    mc.tabmail.tabInfo[mc.tabmail.tabContainer.childNodes.length - 1];
+    mc.tabmail.tabInfo[mc.tabmail.tabContainer.allTabs.length - 1];
   mark_action("fdh", "open_selected_message_in_new_tab",
               ["message", mc.folderDisplay.selectedMessage,
                "background?", Boolean(aBackground),
                "new tab", _jsonize_tabmail_tab(newTab),
                "current tab", _jsonize_tabmail_tab(mc.tabmail.currentTabInfo)]);
   return newTab;
 }
 
@@ -784,17 +784,17 @@ function assert_tab_mode_name(aTab, aMod
 }
 
 /**
  * Assert that the number of tabs open matches the value given.
  *
  * @param aNumber The number of tabs that should be open.
  */
 function assert_number_of_tabs_open(aNumber) {
-  let actualNumber = mc.tabmail.tabContainer.childNodes.length;
+  let actualNumber = mc.tabmail.tabContainer.allTabs.length;
   if (actualNumber != aNumber)
     mark_failure(["There should be " + aNumber + " tabs open, but there " +
                   "are actually " + actualNumber + " tabs open. Tabs:",
                    mc.tabmail.tabInfo]);
 }
 
 /**
  * Assert that the given tab's title is based on the provided folder or
@@ -835,17 +835,17 @@ function assert_tab_has_title(aTab, aTit
  */
 function close_tab(aTabToClose) {
   mark_action("fdh", "close_tab", [aTabToClose]);
 
   if (typeof aTabToClose == "number")
     aTabToClose = mc.tabmail.tabInfo[aTabToClose];
 
   // get the current tab count so we can make sure the tab actually opened.
-  let preCount = mc.tabmail.tabContainer.childNodes.length;
+  let preCount = mc.tabmail.tabContainer.allTabs.length;
 
   // If we're closing the current tab, a message or summary might be displayed
   // in the tab we'll select next.
   let nextTab = null;
   if (aTabToClose == mc.tabmail.currentTabInfo) {
     let selectedIndex = mc.tabmail.tabContainer.selectedIndex;
     let nextIndex = (selectedIndex == preCount - 1) ? selectedIndex - 1 :
       selectedIndex + 1;
@@ -861,17 +861,17 @@ function close_tab(aTabToClose) {
     if (mc.folderDisplay.selectedCount)
       wait_for_message_display_completion(mc, true);
     // otherwise wait for the pane to end up blank
     else
       wait_for_blank_content_pane();
   }
 
   // check that the tab count decreased
-  if (mc.tabmail.tabContainer.childNodes.length != preCount - 1)
+  if (mc.tabmail.tabContainer.allTabs.length != preCount - 1)
     throw new Error("The tab never actually got closed!");
 }
 
 /**
  * Close a message window by calling window.close() on the controller.
  */
 function close_message_window(aController) {
   mark_action("fdh", "close_message_window", []);
@@ -1164,17 +1164,17 @@ function right_click_on_row(aViewIndex) 
  */
 function middle_click_on_row(aViewIndex) {
   let msgHdr = mc.dbView.getMsgHdrAt(aViewIndex);
   mark_action("fdh", "middle_click_on_row",
               ["index", aViewIndex, "message header", msgHdr]);
   _row_click_helper(mc, mc.threadTree, aViewIndex, 1);
   // We append new tabs at the end, so return the last tab
   mark_action("fdh", "/middle_click_on_row", []);
-  return [mc.tabmail.tabInfo[mc.tabmail.tabContainer.childNodes.length - 1],
+  return [mc.tabmail.tabInfo[mc.tabmail.tabContainer.allTabs.length - 1],
           msgHdr];
 }
 
 /**
  * Assert that the given row index is currently visible in the thread pane view.
  */
 function assert_row_visible(aViewIndex) {
   let tree = mc.threadTree;
@@ -1377,17 +1377,17 @@ function right_click_on_folder(aFolder) 
  * @return [The new tab, the view index that you clicked on.]
  */
 function middle_click_on_folder(aFolder) {
   // Figure out the view index
   let viewIndex = mc.folderTreeView.getIndexOfFolder(aFolder);
   mark_action("fdh", "middle_click_on_folder", [aFolder]);
   _row_click_helper(mc, mc.folderTree, viewIndex, 1);
   // We append new tabs at the end, so return the last tab
-  return [mc.tabmail.tabInfo[mc.tabmail.tabContainer.childNodes.length - 1],
+  return [mc.tabmail.tabInfo[mc.tabmail.tabContainer.allTabs.length - 1],
           viewIndex];
 }
 
 /**
  * Get a reference to the smart folder with the given name.
  *
  * @param aFolderName The name of the smart folder (e.g. "Inbox").
  * @returns An nsIMsgFolder representing the smart folder with the given name.
--- a/mail/test/mozmill/tabmail/test-tabmail-dragndrop.js
+++ b/mail/test/mozmill/tabmail/test-tabmail-dragndrop.js
@@ -98,35 +98,35 @@ function test_tab_reorder_tabbar() {
 
   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]);
 
-  let tab1 = mc.tabmail.tabContainer.childNodes[1];
-  let tab3 = mc.tabmail.tabContainer.childNodes[3];
+  let tab1 = mc.tabmail.tabContainer.allTabs[1];
+  let tab3 = mc.tabmail.tabContainer.allTabs[3];
 
   drag_n_drop_element(tab1, mc.window, tab3, mc.window, 0.75, 0.0,
                       mc.tabmail.tabContainer);
 
   wait_for_message_display_completion(mc);
 
   // if every thing went well...
   assert_number_of_tabs_open(5);
 
   // ... we should find tab1 at the third position...
-  assert_equals(tab1, mc.tabmail.tabContainer.childNodes[3],
+  assert_equals(tab1, mc.tabmail.tabContainer.allTabs[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],
+  assert_true(tab3 == mc.tabmail.tabContainer.allTabs[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],
@@ -172,32 +172,32 @@ function test_tab_reorder_window() {
 
   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];
+  let tabA = mc.tabmail.tabContainer.allTabs[1];
   assert_true(tabA, "No movable Tab");
 
   // We drop onto the Folder Tab, it is guaranteed to exist.
-  let tabB = mc2.tabmail.tabContainer.childNodes[0];
+  let tabB = mc2.tabmail.tabContainer.allTabs[0];
   assert_true(tabB, "No movable Tab");
 
   drag_n_drop_element(tabA, mc.window, tabB, mc2.window, 0.75, 0.0,
                       mc.tabmail.tabContainer);
 
   wait_for_message_display_completion(mc2);
 
-  assert_true(mc.tabmail.tabContainer.childNodes.length == 1,
+  assert_true(mc.tabmail.tabContainer.allTabs.length == 1,
     "Moving tab to new window failed, tab still in old window");
 
-  assert_true(mc2.tabmail.tabContainer.childNodes.length == 2,
+  assert_true(mc2.tabmail.tabContainer.allTabs.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
  */
@@ -218,37 +218,37 @@ function test_tab_reorder_detach() {
 
   // ... if every thing works we should expect a new window...
   plan_for_new_window("mail:3pane");
 
   // ... now start dragging
 
   mc.tabmail.switchToTab(1);
 
-  let tab1 = mc.tabmail.tabContainer.childNodes[1];
+  let tab1 = mc.tabmail.tabContainer.allTabs[1];
   let dropContent = mc.e("tabpanelcontainer");
 
   let dt = synthesize_drag_start(mc.window, tab1, mc.tabmail.tabContainer);
 
   synthesize_drag_over(mc.window, dropContent, dt);
 
   // notify tab1 drag has ended
   let dropRect = dropContent.getBoundingClientRect();
   synthesize_drag_end(mc.window, dropContent, tab1, dt,
       { screenX: (dropContent.screenX + dropRect.width / 2),
         screenY: (dropContent.screenY + dropRect.height / 2) });
 
   // ... 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,
+  assert_true(mc.tabmail.tabContainer.allTabs.length == 1,
       "Moving tab to new window failed, tab still in old window");
 
-  assert_true(mc2.tabmail.tabContainer.childNodes.length == 2,
+  assert_true(mc2.tabmail.tabContainer.allTabs.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.
  */
@@ -286,17 +286,17 @@ function test_tab_undo() {
   // 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]);
 }
 
 function _synthesizeRecentlyClosedMenu() {
-  mc.rightClick(new elib.Elem(mc.tabmail.tabContainer.childNodes[1]));
+  mc.rightClick(new elib.Elem(mc.tabmail.tabContainer.allTabs[1]));
 
   let tabContextMenu = mc.window.document.getElementById("tabContextMenu");
   wait_for_popup_to_open(tabContextMenu);
 
   let recentlyClosedTabs = tabContextMenu
                            .querySelector('[anonid="recentlyClosedTabs"]');
 
   EventUtils.synthesizeMouse(recentlyClosedTabs, 5, 5, {}, mc.window);
--- a/mail/themes/linux/mail/tabmail.css
+++ b/mail/themes/linux/mail/tabmail.css
@@ -52,17 +52,16 @@ tabpanels {
 
 .tabmail-tab[selected]:focus > .tab-stack > .tab-content > .tab-label-container {
   border-color: -moz-DialogText;
 }
 
 /* Tab drag and drop */
 .tab-drop-indicator {
   list-style-image: url(icons/dragIndicator.png);
-  margin-bottom: -11px;
 }
 
 /* Tabstrip close button */
 .tabs-closebutton {
   margin-top: 1px;
 }
 
 /**
--- a/mail/themes/windows/mail/tabmail.css
+++ b/mail/themes/windows/mail/tabmail.css
@@ -37,17 +37,16 @@ tabpanels {
 
 .tabmail-tab:focus > .tab-stack > .tab-content > .tab-label-container {
   border-color: -moz-DialogText;
 }
 
 /* Tab DnD indicator */
 .tab-drop-indicator {
   list-style-image: url(icons/dragIndicator.png);
-  margin-bottom: -11px;
 }
 
 /**
  * close buttons
  */
 
 .tabs-closebutton-box > .tabs-closebutton {
   margin-top: 1px;