var gAllTabsButton;
window.addEventListener("load", function () {
  setTimeout(function () {
    tabPreviews.init();
    if (gPrefService.getBoolPref("browser.ctrlTab.mostRecentlyUsed") &&
        (!gPrefService.prefHasUserValue("browser.ctrlTab.disallowForScreenReaders") ||
         !gPrefService.getBoolPref("browser.ctrlTab.disallowForScreenReaders")))
      ctrlTab.init();

    gAllTabsButton = document.getAnonymousElementByAttribute(
                       gBrowser.tabContainer, "anonid", "alltabs-button");
    gAllTabsButton.removeAttribute("type");
    gAllTabsButton.setAttribute("command", "Browser:ShowAllTabs");
  }, 100);
}, false);
window.addEventListener("unload", function () {
  tabPreviews.uninit();
  ctrlTab.uninit();
  allTabs.uninit();
}, false);

/**
 * Tab previews utility, produces thumbnails
 */
var tabPreviews = {
  aspectRatio: 0.5625, // 16:9
  init: function tabPreviews__init() {
    this.width = Math.ceil(screen.availWidth / 8);
    this.height = Math.round(this.width * this.aspectRatio);

    gBrowser.tabContainer.addEventListener("TabSelect", this, false);
    gBrowser.tabContainer.addEventListener("SSTabRestored", this, false);
  },
  uninit: function tabPreviews__uninit() {
    gBrowser.tabContainer.removeEventListener("TabSelect", this, false);
    gBrowser.tabContainer.removeEventListener("SSTabRestored", this, false);
    this._selectedTab = null;
  },
  get: function tabPreviews__get(aTab) {
    if (aTab.__thumbnail_lastURI &&
        aTab.__thumbnail_lastURI != aTab.linkedBrowser.currentURI.spec) {
      aTab.__thumbnail = null;
      aTab.__thumbnail_lastURI = null;
    }
    return aTab.__thumbnail || this.capture(aTab, !aTab.hasAttribute("busy"));
  },
  capture: function tabPreviews__capture(aTab, aStore) {
    var thumbnail = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
    thumbnail.mozOpaque = true;
    thumbnail.height = this.height;
    thumbnail.width = this.width;

    var ctx = thumbnail.getContext("2d");
    var win = aTab.linkedBrowser.contentWindow;
    var snippetWidth = win.innerWidth * .6;
    var scale = this.width / snippetWidth;
    ctx.scale(scale, scale);
    ctx.drawWindow(win, win.scrollX, win.scrollY,
                   snippetWidth, snippetWidth * this.aspectRatio, "rgb(255,255,255)");

    if (aStore) {
      aTab.__thumbnail = thumbnail;
      aTab.__thumbnail_lastURI = aTab.linkedBrowser.currentURI.spec;
    }
    return thumbnail;
  },
  handleEvent: function tabPreviews__handleEvent(event) {
    switch (event.type) {
      case "TabSelect":
        if (this._selectedTab &&
            this._selectedTab.parentNode &&
            !this._pendingUpdate) {
          // Generate a thumbnail for the tab that was selected.
          // The timeout keeps the UI snappy and prevents us from generating thumbnails
          // for tabs that will be closed. During that timeout, don't generate other
          // thumbnails in case multiple TabSelect events occur fast in succession.
          this._pendingUpdate = true;
          setTimeout(function (self, aTab) {
            self._pendingUpdate = false;
            if (aTab.parentNode && !aTab.hasAttribute("busy"))
              self.capture(aTab, true);
          }, 2000, this, this._selectedTab);
        }
        this._selectedTab = event.target;
        break;
      case "SSTabRestored":
        this.capture(event.target, true);
        break;
    }
  }
};

/**
 * Ctrl-Tab panel
 */
var ctrlTab = {
  get panel () {
    delete this.panel;
    return this.panel = document.getElementById("ctrlTab-panel");
  },
  get showAllButton () {
    delete this.showAllButton;
    return this.showAllButton = document.getElementById("ctrlTab-showAll");
  },
  get thumbnails () {
    delete this.thumbnails;
    return this.thumbnails = this.panel.getElementsByClassName("ctrlTab-thumbnail");
  },
  get recentlyUsedLimit () {
    delete this.recentlyUsedLimit;
    return this.recentlyUsedLimit = gPrefService.getIntPref("browser.ctrlTab.recentlyUsedLimit");
  },
  get keys () {
    var keys = {};
    ["close", "find", "selectAll"].forEach(function (key) {
      keys[key] = document.getElementById("key_" + key)
                          .getAttribute("key")
                          .toLocaleLowerCase().charCodeAt(0);
    });
    delete this.keys;
    return this.keys = keys;
  },
  _selectedIndex: 0,
  get selected () this._selectedIndex < 0 ?
                    document.activeElement :
                    this.thumbnails.item(this._selectedIndex),
  get isOpen   () this.panel.state == "open" || this.panel.state == "showing" || this._timer,
  get tabCount () this.tabList.length,

  get tabList () {
    if (this._tabList)
      return this._tabList;

    var list = Array.slice(gBrowser.mTabs);

    if (this._closing)
      this.detachTab(this._closing, list);

    for (let i = 0; i < gBrowser.tabContainer.selectedIndex; i++)
      list.push(list.shift());

    if (this.recentlyUsedLimit != 0) {
      let recentlyUsedTabs = this._recentlyUsedTabs;
      if (this.recentlyUsedLimit > 0)
        recentlyUsedTabs = this._recentlyUsedTabs.slice(0, this.recentlyUsedLimit);
      for (let i = recentlyUsedTabs.length - 1; i >= 0; i--) {
        list.splice(list.indexOf(recentlyUsedTabs[i]), 1);
        list.unshift(recentlyUsedTabs[i]);
      }
    }

    return this._tabList = list;
  },

  init: function ctrlTab__init() {
    if (this._recentlyUsedTabs)
      return;
    this._recentlyUsedTabs = [gBrowser.selectedTab];

    var tabContainer = gBrowser.tabContainer;
    tabContainer.addEventListener("TabOpen", this, false);
    tabContainer.addEventListener("TabSelect", this, false);
    tabContainer.addEventListener("TabClose", this, false);

    gBrowser.mTabBox.handleCtrlTab = false;
    document.addEventListener("keypress", this, false);
  },

  uninit: function ctrlTab__uninit() {
    this._recentlyUsedTabs = null;

    var tabContainer = gBrowser.tabContainer;
    tabContainer.removeEventListener("TabOpen", this, false);
    tabContainer.removeEventListener("TabSelect", this, false);
    tabContainer.removeEventListener("TabClose", this, false);

    this.panel.removeEventListener("popupshown", this, false);
    this.panel.removeEventListener("popuphiding", this, false);
    this.panel.removeEventListener("popuphidden", this, false);
    document.removeEventListener("keypress", this, false);
    gBrowser.mTabBox.handleCtrlTab = true;
  },

  updatePreviews: function ctrlTab__updatePreviews() {
    for (let i = 0; i < this.thumbnails.length; i++)
      this.updatePreview(this.thumbnails[i], this.tabList[i]);

    this.showAllButton.label =
      gAllTabsButton.tooltipText + " (" + this.tabCount + ")";
//      gNavigatorBundle.getFormattedString("ctrlTab.showAll.label", [this.tabCount]);
  },

  updatePreview: function ctrlTab__updatePreview(aThumbnail, aTab) {
    if (aThumbnail == this.showAllButton)
      return;

    do {
      if (aThumbnail._tab) {
        if (aThumbnail._tab == aTab)
          break;
        aThumbnail._tab.removeEventListener("DOMAttrModified", this, false);
      }
      aThumbnail._tab = aTab;
      if (aTab)
        aTab.addEventListener("DOMAttrModified", this, false);
    } while (false);

    if (aThumbnail.firstChild)
      aThumbnail.removeChild(aThumbnail.firstChild);
    if (aTab) {
      aThumbnail.appendChild(tabPreviews.get(aTab));
      aThumbnail.setAttribute("label", aTab.label);
      aThumbnail.setAttribute("tooltiptext", aTab.label);
      aThumbnail.setAttribute("crop", aTab.crop);
      aThumbnail.setAttribute("canvaswidth", tabPreviews.width);
      if (aTab.image)
        aThumbnail.setAttribute("icon", aTab.image);
      else
        aThumbnail.removeAttribute("icon");
      aThumbnail.hidden = false;
    } else {
      aThumbnail.hidden = true;
      aThumbnail.removeAttribute("label");
      aThumbnail.removeAttribute("tooltiptext");
      aThumbnail.removeAttribute("icon");
    }
  },

  advanceFocus: function ctrlTab_advanceFocus(aForward) {
    if (this.panel.state == "open") {
      if (aForward)
        document.commandDispatcher.advanceFocus();
      else
        document.commandDispatcher.rewindFocus();
    } else {
      do {
        this._selectedIndex += aForward ? 1 : -1;
        if (this._selectedIndex < 0)
          this._selectedIndex = this.thumbnails.length - 1;
        else if (this._selectedIndex >= this.thumbnails.length)
          this._selectedIndex = 0;
      } while (this.selected.hidden);
    }

    if (this._timer) {
      clearTimeout(this._timer);
      this._timer = null;
      this._openPanel();
    }
  },

  _mouseOverFocus: function ctrlTab_mouseOverFocus(aThumbnail) {
    if (this._trackMouseOver)
      aThumbnail.focus();
  },

  pick: function ctrlTab_pick(aThumbnail) {
    if (!this.tabCount)
      return;

    var select = (aThumbnail || this.selected);

    if (select == this.showAllButton) {
      this.close();
      document.getElementById("Browser:ShowAllTabs").doCommand();
    } else {
      this.close(select._tab);
    }
  },

  remove: function ctrlTab_remove(aThumbnail) {
    if (aThumbnail._tab)
      gBrowser.removeTab(aThumbnail._tab);
  },

  attachTab: function ctrlTab__attachTab(aTab, aPos) {
    if (aPos == 0)
      this._recentlyUsedTabs.unshift(aTab);
    else if (aPos)
      this._recentlyUsedTabs.splice(aPos, 0, aTab);
    else
      this._recentlyUsedTabs.push(aTab);
  },
  detachTab: function ctrlTab__detachTab(aTab, aTabs) {
    var tabs = aTabs || this._recentlyUsedTabs;
    var i = tabs.indexOf(aTab);
    if (i >= 0)
      tabs.splice(i, 1);
  },

  open: function ctrlTab_open() {
    if (this.isOpen)
      return;

    allTabs.close();

    document.addEventListener("keyup", this, true);

    this.updatePreviews();
    this._selectedIndex = 1;

    // Add a slight delay before showing the UI, so that a quick
    // "ctrl-tab" keypress just flips back to the MRU tab.
    this._timer = setTimeout(function (self) {
      self._timer = null;
      self._openPanel();
    }, 100, this);
  },

  _openPanel: function ctrlTab_openPanel() {
    this.panel.addEventListener("popupshown", this, false);
    this.panel.addEventListener("popuphiding", this, false);
    this.panel.addEventListener("popuphidden", this, false);

    this._prevFocus = document.commandDispatcher.focusedElement ||
                      document.commandDispatcher.focusedWindow;

    this.panel.hidden = false;
    var visibleTabThumbnails = Math.min(this.thumbnails.length - 1, this.tabCount);
    this.panel.width = Math.min(screen.availWidth * .99,
                                tabPreviews.width * 1.25 * visibleTabThumbnails);
    var estimateHeight = tabPreviews.height * 1.2 + 50;
    this.panel.openPopupAtScreen(screen.availLeft + (screen.availWidth - this.panel.width) / 2,
                                 screen.availTop + (screen.availHeight - estimateHeight) / 2,
                                 false);
  },

  close: function ctrlTab_close(aTabToSelect) {
    if (!this.isOpen)
      return;

    if (this._timer) {
      clearTimeout(this._timer);
      this._timer = null;
      this._cleanupUI();
      if (aTabToSelect)
        gBrowser.selectedTab = aTabToSelect;
      return;
    }

    this._tabToSelect = aTabToSelect;
    this.panel.hidePopup();
  },

  _cleanupUI: function ctrlTab_cleanupUI() {
    document.removeEventListener("keyup", this, true);

    Array.forEach(this.thumbnails, function (thumbnail) {
      this.updatePreview(thumbnail, null);
    }, this);

    this._tabList = null;
  },

  onKeyPress: function ctrlTab__onKeyPress(event) {
    var isOpen = this.isOpen;

    if (isOpen) {
      event.preventDefault();
      event.stopPropagation();
    }

    switch (event.keyCode) {
      case event.DOM_VK_TAB:
        if (event.ctrlKey && !event.altKey && !event.metaKey) {
          if (isOpen) {
            this.advanceFocus(!event.shiftKey);
          } else if (!event.shiftKey) {
            event.preventDefault();
            event.stopPropagation();
            if (gBrowser.mTabs.length > 2) {
              this.open();
            } else if (gBrowser.mTabs.length == 2) {
              gBrowser.selectedTab = gBrowser.selectedTab.nextSibling ||
                                     gBrowser.selectedTab.previousSibling;
            }
          }
        }
        break;
      default:
        if (isOpen && event.ctrlKey) {
          switch (event.charCode) {
            case this.keys.close:
              this.remove(this.selected);
              break;
            case this.keys.find:
            case this.keys.selectAll:
              this.close();
              document.getElementById("Browser:ShowAllTabs").doCommand();
              break;
          }
        }
    }
  },

  onPopupHiding: function ctrlTab__onPopupHiding() {
    this._cleanupUI();

    this._prevFocus.focus();
    this._prevFocus = null;

    if (this._tabToSelect) {
      gBrowser.selectedTab = this._tabToSelect;
      this._tabToSelect = null;
    }
  },

  removeClosingTabFromUI: function ctrlTab__removeClosingTabFromUI(aTab) {
    if (this.tabCount == 2) {
      this.close();
      return;
    }

    this._closing = aTab;
    this._tabList = null;
    this.updatePreviews();
    this._closing = null;

    if (this.selected.hidden)
      this.advanceFocus(false);
    if (this.selected == this.showAllButton)
      this.advanceFocus(false);

    // If the current tab is removed, another tab can steal our focus.
    if (aTab == gBrowser.selectedTab && this.panel.state == "open") {
      setTimeout(function (selected) {
        selected.focus();
      }, 0, this.selected);
    }
  },

  handleEvent: function ctrlTab__handleEvent(event) {
    switch (event.type) {
      case "DOMAttrModified":
        // tab attribute modified (e.g. label, crop, busy, image)
        for (let i = this.thumbnails.length - 1; i >= 0; i--) {
          if (this.thumbnails[i]._tab == event.target) {
            this.updatePreview(this.thumbnails[i], event.target);
            break;
          }
        }
        break;
      case "TabSelect":
        this.detachTab(event.target);
        this.attachTab(event.target, 0);
        break;
      case "TabOpen":
        this.attachTab(event.target, 1);
        break;
      case "TabClose":
        this.detachTab(event.target);
        if (this.isOpen)
          this.removeClosingTabFromUI(event.target);
        break;
      case "keypress":
        this.onKeyPress(event);
        break;
      case "keyup":
        if (event.keyCode == event.DOM_VK_CONTROL)
          this.pick();
        break;
      case "popupshown":
        this.selected.focus();
        this._selectedIndex = -1;

        this._trackMouseOver = false;
        setTimeout(function (self) {
          if (self.isOpen)
            self._trackMouseOver = true;
        }, 0, this);
        break;
      case "popuphiding":
        if (event.target == this.panel)
          this.onPopupHiding();
        break;
      case "popuphidden":
        if (event.target == this.panel) {
          // Destroy the widget in order to prevent outdated content
          // when re-opening the panel.
          this.panel.hidden = true;
        }
        break;
    }
  }
};


/**
 * All Tabs panel
 */
var allTabs = {
  get panel () {
    delete this.panel;
    return this.panel = document.getElementById("allTabs-panel");
  },
  get filterField () {
    delete this.filterField;
    return this.filterField = document.getElementById("allTabs-filter");
  },
  get container () {
    delete this.container;
    return this.container = document.getElementById("allTabs-container");
  },
  get tabCloseButton () {
    delete this.tabCloseButton;
    return this.tabCloseButton = document.getElementById("allTabs-tab-close-button");
  },
  get _browserCommandSet () {
    delete this._browserCommandSet;
    return this._browserCommandSet = document.getElementById("mainCommandSet");
  },
  get isOpen () this.panel.state == "open" || this.panel.state == "showing",

  init: function allTabs_init() {
    if (this._initiated)
      return;
    this._initiated = true;
	
    Array.forEach(gBrowser.mTabs, function (tab) {
      this._addBox(tab);
    }, this);

    gBrowser.tabContainer.addEventListener("TabOpen", this, false);
    gBrowser.tabContainer.addEventListener("TabMove", this, false);
    gBrowser.tabContainer.addEventListener("TabClose", this, false);
  },

  uninit: function allTabs_uninit() {
    if (!this._initiated)
      return;

    gBrowser.tabContainer.removeEventListener("TabOpen", this, false);
    gBrowser.tabContainer.removeEventListener("TabMove", this, false);
    gBrowser.tabContainer.removeEventListener("TabClose", this, false);

    this.panel.removeEventListener("popuphidden", this, false);
    this.panel.removeEventListener("popupshown", this, false);
    this.panel.removeEventListener("keypress", this, true);
    this._browserCommandSet.removeEventListener("command", this, false);

    while (this.container.hasChildNodes())
      this.container.removeChild(this.container.firstChild);

    this._initiated = false;
  },

  pick: function allTabs_pick(aBox) {
    if (!aBox)
      aBox = this._firstVisibleBox;

    if (aBox)
      gBrowser.selectedTab = aBox._tab;

    this.close();
  },

  closeTab: function allTabs_closeTab(event) {
    if (event.type != "click" || event.button == 1) {
      this.filterField.focus();
      gBrowser.removeTab(event.currentTarget._targetBox._tab);
    }
  },

  filter: function allTabs_filter() {
    var filter = this.filterField.value.split(/\s+/g);
    this._visible = 0;
    Array.forEach(this.container.childNodes, function (box) {
      var tab = box._tab;
      var matches = 0;
      if (filter.length) {
        let tabstring = tab.linkedBrowser.currentURI.spec;
        try {
          tabstring = decodeURI(tabstring);
        } catch (e) {}
        tabstring = tab.label + " " + tab.label.toLocaleLowerCase() + " " + tabstring;
        for (let i = 0; i < filter.length; i++)
          matches += tabstring.indexOf(filter[i]) > -1;
      }
      if (matches < filter.length) {
        box.hidden = true;
        tab.removeEventListener("DOMAttrModified", this, false);
      }
      else {
        this._visible++;
        this._updatePreview(box);
        box.hidden = false;
        tab.addEventListener("DOMAttrModified", this, false);
      }
    }, this);

    this._reflow();
  },

  open: function allTabs_open(aToggle) {
    this.init();

    if (this.isOpen) {
      if (aToggle)
        this.close();
      return;
    }

    this.filter();

    this.panel.hidden = false;
    this.panel.popupBoxObject.setConsumeRollupEvent(Ci.nsIPopupBoxObject.ROLLUP_CONSUME);
    var estimateHeight = (this._maxHeight + parseInt(this.container.maxHeight) + 50) / 2;
    this.panel.openPopupAtScreen(screen.availLeft + (screen.availWidth - this._maxWidth) / 2,
                                 screen.availTop + (screen.availHeight - estimateHeight) / 2,
                                 false);
    this.panel.addEventListener("popuphidden", this, false);
    this.panel.addEventListener("popupshown", this, false);
    this.panel.addEventListener("keypress", this, true);
    this._browserCommandSet.addEventListener("command", this, false);
  },

  close: function allTabs_close() {
    this.panel.hidePopup();
  },

  handleEvent: function allTabs_handleEvent(event) {
    switch (event.type) {
      case "DOMAttrModified":
        // tab attribute modified (e.g. label, crop, busy, image)
        this._updatePreview(this._getBox(event.target));
        break;
      case "TabOpen":
        if (this.isOpen)
          this.close();
        this._addBox(event.target);
        break;
      case "TabMove":
        if (event.target.nextSibling)
          this.container.insertBefore(this._getBox(event.target),
                                      this._getBox(event.target.nextSibling));
        else
          this.container.appendChild(this._getBox(event.target));
        break;
      case "TabClose":
        this._removeBox(this._getBox(event.target));
        break;
      case "keypress":
        switch (event.keyCode) {
          case event.DOM_VK_UP:
          case event.DOM_VK_DOWN:
            if (event.target != this.filterField)
              this._advanceFocusVertically(event);
            break;
          case event.DOM_VK_RETURN:
            if (event.target == this.filterField) {
              // If there's a pending search, kick it off now.
              if (this.filterField._timer)
                this.filter();
              this.pick();
              event.preventDefault();
              event.stopPropagation();
            }
            break;
        }
        break;
      case "popupshown":
        if (event.target == this.panel) {
          this.filterField.focus();
          this.filterField.setAttribute("emptytext", this.filterField.tooltipText);
        }
        break;
      case "popuphidden":
        if (event.target == this.panel)
          this._onPopupHidden();
        break;
      case "command":
        if (event.target.id != "Browser:ShowAllTabs") {
          // Close the panel when there's a browser command executing in the background.
          this.close();
        }
        break;
    }
  },

  _visible: 0,
  get _maxWidth () screen.availWidth * .9,
  get _maxHeight () screen.availHeight * .75,
  get _stack () {
    delete this._stack;
    return this._stack = document.getElementById("allTabs-stack");
  },
  get _boxLabelHeight () {
    delete this._boxLabelHeight;
    return this._boxLabelHeight = parseInt(getComputedStyle(this.container.firstChild, "").lineHeight);
  },

  get _firstVisibleBox () {
    if (!this._visible)
      return null;
    var boxes = this.container.childNodes;
    for (let i = 0; i < boxes.length; i++) {
      if (!boxes[i].hidden)
        return boxes[i];
    }
    return null;
  },

  get _lastVisibleBox () {
    var boxes = this.container.childNodes;
    for (let i = boxes.length - 1; i >= 0; i--) {
      if (!boxes[i].hidden)
        return boxes[i];
    }
    return null;
  },

  _reflow: function allTabs_reflow() {
    this._updateTabCloseButton();

    // the size of the box relative to the preview image
    const REL_BOX_THUMBNAIL = 1.2;

    var maxHeight = this._maxHeight;
    var maxWidth = this._maxWidth;
    var rel = tabPreviews.height / tabPreviews.width;
    var rows, boxHeight, boxWidth, outerHeight;
    var boxMaxWidth = tabPreviews.width * REL_BOX_THUMBNAIL;
    this._columns = Math.floor(maxWidth / boxMaxWidth);
    do {
      rows = Math.ceil(this._visible / this._columns);
      boxWidth = Math.min(boxMaxWidth, Math.round(maxWidth / this._columns));
      boxHeight = Math.round(boxWidth * rel);
      outerHeight = boxHeight + this._boxLabelHeight;
    } while (rows * outerHeight > maxHeight && ++this._columns);

    var outerWidth = boxWidth;
    {
      let innerWidth = Math.ceil(boxWidth / REL_BOX_THUMBNAIL);
      let innerHeight = Math.ceil(boxHeight / REL_BOX_THUMBNAIL);
      let verticalPadding = innerHeight * (REL_BOX_THUMBNAIL - 1);
      var canvasStyle = "margin-bottom:" + (verticalPadding * .3) + "px;" +
                        "max-width:" + innerWidth + "px;" +
                        "min-width:" + innerWidth + "px;" +
                        "max-height:" + innerHeight + "px;" +
                        "min-height:" + innerHeight + "px;";
    }

    Array.forEach(this.container.childNodes, function (box) {
      box.setAttribute("minwidth", outerWidth);
      box.setAttribute("height", outerHeight);
      box.setAttribute("canvasstyle", canvasStyle);
      box.removeAttribute("closebuttonhover");
    }, this);

    this._stack.width = maxWidth;
    this.container.width = Math.ceil(outerWidth * Math.min(this._columns, this._visible));
    this.container.left = Math.round((maxWidth - this.container.width) / 2);
    this.container.maxWidth = maxWidth - this.container.left;
    this.container.maxHeight = rows * outerHeight;
  },

  _addBox: function allTabs_addBox(aTab) {
    var box = document.createElement("button");
    box.setAttribute("class", "allTabs-thumbnail");
    box._tab = aTab;
    return this.container.appendChild(box);
  },

  _removeBox: function allTabs_removeBox(aBox) {
    var updateUI = (this.isOpen && !aBox.hidden);
    aBox._tab.removeEventListener("DOMAttrModified", this, false);
    aBox._tab = null;
    this.container.removeChild(aBox);
    if (updateUI) {
      this._visible--;
      this._reflow();
      this.filterField.focus();
    }
  },

  _getBox: function allTabs_getBox(aTab) {
    var boxes = this.container.childNodes;
    for (let i = 0; i < boxes.length; i++)
      if (boxes[i]._tab == aTab)
        return boxes[i];
    return null;
  },

  _updateTabCloseButton: function allTabs_updateTabCloseButton(event) {
    if (event && event.target == this.tabCloseButton)
      return;

    if (this.tabCloseButton._targetBox) {
      if (event && event.target == this.tabCloseButton._targetBox)
        return;

      this.tabCloseButton._targetBox.removeAttribute("closebuttonhover");
    }

    if (event &&
        event.target.parentNode == this.container &&
        (event.target._tab.previousSibling || event.target._tab.nextSibling)) {
      let thumbnail = event.target.getBoundingClientRect();
      let container = this.container.getBoundingClientRect();
      this.tabCloseButton.left = thumbnail.left -
                                 container.left +
                                 parseInt(this.container.left) +
                                 (this.panel.getAttribute("chromedir") ==
                                    (navigator.platform.indexOf("Mac") == -1 ? "ltr" : "rtl") ?
                                  thumbnail.width -
                                  this.tabCloseButton.boxObject.width : 0);
      this.tabCloseButton.top = thumbnail.top -
                                container.top;
      this.tabCloseButton._targetBox = event.target;
      this.tabCloseButton.style.visibility = "visible";
      event.target.setAttribute("closebuttonhover", "true");
    } else {
      this.tabCloseButton.style.visibility = "hidden";
      this.tabCloseButton.left = this.tabCloseButton.top = 0;
      this.tabCloseButton._targetBox = null;
    }
  },

  _updatePreview: function allTabs_updatePreview(aBox) {
    aBox.setAttribute("label", aBox._tab.label);
    aBox.setAttribute("tooltiptext", aBox._tab.label);
    aBox.setAttribute("crop", aBox._tab.crop);
    if (aBox._tab.image)
      aBox.setAttribute("icon", aBox._tab.image);
    else
      aBox.removeAttribute("icon");

    var preview = tabPreviews.get(aBox._tab);
    if (aBox.firstChild) {
      if (aBox.firstChild == preview)
        return;
      aBox.removeChild(aBox.firstChild);
    }
    aBox.appendChild(preview);
  },

  _onPopupHidden: function allTabs_onPopupHidden() {
    Array.forEach(this.container.childNodes, function (box) {
      box._tab.removeEventListener("DOMAttrModified", this, false);
    }, this);

    this.filterField.removeAttribute("emptytext");
    this.filterField.value = "";
    this._updateTabCloseButton();

    // Destroy the widget in order to prevent outdated content
    // when re-opening the panel.
    this.panel.hidden = true;
  },

  _onKeyPress: function allTabs_onKeyPress(event) {
    if (event.keyCode == event.DOM_VK_ESCAPE) {
      this.close();
      event.preventDefault();
      event.stopPropagation();
      return;
    }

    if (event.target == this.filterField) {
      switch (event.keyCode) {
        case event.DOM_VK_UP:
          if (this._visible) {
            this._lastVisibleBox.focus();
            event.preventDefault();
            event.stopPropagation();
          }
          break;
        case event.DOM_VK_DOWN:
          if (this._visible) {
            this._firstVisibleBox.focus();
            event.preventDefault();
            event.stopPropagation();
          }
          break;
      }
    }
  },

  _advanceFocusVertically: function allTabs_advanceFocusVertically(event) {
    var box = document.commandDispatcher.focusedElement;
    if (!box || box.parentNode != this.container)
      return;

    var up = (event.keyCode == event.DOM_VK_UP);
    var boxes = Array.filter(this.container.childNodes, function (box) !box.hidden);
    if (up && box == boxes[0] ||
        !up && box == boxes[boxes.length - 1])
      return;

    var rows = Math.ceil(boxes.length / this._columns);
    if (rows == 1)
      return;

    var i = boxes.indexOf(box);
    var row = Math.floor(i / this._columns);
    row += (up ? -1 : 1);
    if (row < 0) {
      row = rows - 1;
      i--;
    } else if (row >= rows) {
      row = 0;
      i++;
    }
    i %= this._columns;
    i += row * this._columns;
    if (i >= boxes.length) {
      if (up)
        i -= this._columns;
      else
        i %= this._columns;
    }

    if (boxes[i] != box) {
      boxes[i].focus();
      event.stopPropagation();
    }
  }
};