browser/base/content/tabbrowser.xml
author Dão Gottwald <dao@mozilla.com>
Sun, 24 Jan 2010 11:20:57 +0100
changeset 37448 f077fca0d235919894a3703b553accee7ef87024
parent 37304 3198c740b660e85f2ea7f496aa51a80e8914c386
child 37917 b29ffe328325e3a4cf3baefcf6b909a3eae6fa1c
permissions -rw-r--r--
Bug 504475 - closing last tab shows tab bar with "Always show the tab bar" off, and browser.tabs.closeWindowWithLastTab = false. r=gavin

<?xml version="1.0"?>

<!-- ***** BEGIN LICENSE BLOCK *****
   - Version: MPL 1.1/GPL 2.0/LGPL 2.1
   -
   - The contents of this file are subject to the Mozilla Public License Version
   - 1.1 (the "License"); you may not use this file except in compliance with
   - the License. You may obtain a copy of the License at
   - http://www.mozilla.org/MPL/
   -
   - Software distributed under the License is distributed on an "AS IS" basis,
   - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
   - for the specific language governing rights and limitations under the
   - License.
   -
   - The Original Code is this file as it was released on March 28, 2001.
   -
   - The Initial Developer of the Original Code is
   - David Hyatt.
   - Portions created by the Initial Developer are Copyright (C) 2001
   - the Initial Developer. All Rights Reserved.
   -
   - Contributor(s):
   -   David Hyatt <hyatt@netscape.com> (Original Author of <tabbrowser>)
   -   Mike Connor <mconnor@steelgryphon.com>
   -   Peter Parente <parente@cs.unc.edu>
   -   Giorgio Maone <g.maone@informaction.com>
   -   Asaf Romano <mozilla.mano@sent.com>
   -   Seth Spitzer <sspitzer@mozilla.org>
   -   Simon Bünzli <zeniko@gmail.com>
   -   Michael Ventnor <ventnor.bugzilla@yahoo.com.au>
   -   Mark Pilgrim <pilgrim@gmail.com>
   -   Dão Gottwald <dao@mozilla.com>
   -   Paul O’Shannessy <paul@oshannessy.com>
   -   Rob Arnold <tellrob@gmail.com>
   -
   - Alternatively, the contents of this file may be used under the terms of
   - either the GNU General Public License Version 2 or later (the "GPL"), or
   - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
   - in which case the provisions of the GPL or the LGPL are applicable instead
   - of those above. If you wish to allow use of your version of this file only
   - under the terms of either the GPL or the LGPL, and not to allow others to
   - use your version of this file under the terms of the MPL, indicate your
   - decision by deleting the provisions above and replace them with the notice
   - and other provisions required by the GPL or the LGPL. If you do not delete
   - the provisions above, a recipient may use your version of this file under
   - the terms of any one of the MPL, the GPL or the LGPL.
   -
   - ***** END LICENSE BLOCK ***** -->

<!DOCTYPE bindings [
<!ENTITY % tabBrowserDTD SYSTEM "chrome://browser/locale/tabbrowser.dtd" >
%tabBrowserDTD;
]>

<bindings id="tabBrowserBindings"
          xmlns="http://www.mozilla.org/xbl"
          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
          xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="tabbrowser">
    <resources>
      <stylesheet src="chrome://browser/content/tabbrowser.css"/>
    </resources>

    <content>
      <xul:stringbundle anonid="tbstringbundle" src="chrome://browser/locale/tabbrowser.properties"/>
      <xul:tabbox anonid="tabbox" flex="1" eventnode="document" xbl:inherits="handleCtrlPageUpDown"
                  onselect="if (!('updateCurrentBrowser' in this.parentNode) || event.target.localName != 'tabpanels') return; this.parentNode.updateCurrentBrowser();">
        <xul:hbox class="tab-drop-indicator-bar" collapsed="true"
                  ondragover="this.parentNode.parentNode._onDragOver(event);"
                  ondragleave="this.parentNode.parentNode._onDragLeave(event);"
                  ondrop="this.parentNode.parentNode._onDrop(event);">
          <xul:hbox class="tab-drop-indicator" mousethrough="always"/>
        </xul:hbox>
        <xul:hbox class="tabbrowser-strip" collapsed="true" tooltip="_child" context="_child"
                  anonid="strip"
                  ondragstart="this.parentNode.parentNode._onDragStart(event);"
                  ondragover="this.parentNode.parentNode._onDragOver(event);"
                  ondrop="this.parentNode.parentNode._onDrop(event);"
                  ondragend="this.parentNode.parentNode._onDragEnd(event);"
                  ondragleave="this.parentNode.parentNode._onDragLeave(event);">
          <xul:tooltip onpopupshowing="return this.parentNode.parentNode.parentNode.createTooltip(event);"/>
          <xul:menupopup anonid="tabContextMenu" onpopupshowing="this.parentNode.parentNode.parentNode.updatePopupMenu(this);">
            <xul:menuitem id="context_newTab" label="&newTab.label;" accesskey="&newTab.accesskey;"
                          command="cmd_newNavigatorTab"/>
            <xul:menuseparator/>
            <xul:menuitem id="context_reloadTab" label="&reloadTab.label;" accesskey="&reloadTab.accesskey;"
                          oncommand="var tabbrowser = this.parentNode.parentNode.parentNode.parentNode;
                                     tabbrowser.reloadTab(tabbrowser.mContextTab);"/>
            <xul:menuitem id="context_reloadAllTabs" label="&reloadAllTabs.label;" accesskey="&reloadAllTabs.accesskey;"
                          tbattr="tabbrowser-multiple"
                          oncommand="var tabbrowser = this.parentNode.parentNode.parentNode.parentNode;
                                     tabbrowser.reloadAllTabs(tabbrowser.mContextTab);"/>
            <xul:menuitem id="context_closeOtherTabs" label="&closeOtherTabs.label;" accesskey="&closeOtherTabs.accesskey;"
                          tbattr="tabbrowser-multiple"
                          oncommand="var tabbrowser = this.parentNode.parentNode.parentNode.parentNode;
                                     tabbrowser.removeAllTabsBut(tabbrowser.mContextTab);"/>
            <xul:menuseparator/>
            <xul:menuitem id="context_openTabInWindow" label="&openTabInNewWindow.label;"
                          accesskey="&openTabInNewWindow.accesskey;"
                          tbattr="tabbrowser-multiple"
                          oncommand="var tabbrowser = this.parentNode.parentNode.parentNode.parentNode;
                                     tabbrowser.replaceTabWithWindow(tabbrowser.mContextTab);"/>
            <xul:menuseparator/>
            <xul:menuitem id="context_bookmarkTab"
                          label="&bookmarkThisTab.label;"
                          accesskey="&bookmarkThisTab.accesskey;"
                          oncommand="BookmarkThisTab();"/>
            <xul:menuitem id="context_bookmarkAllTabs"
                          label="&bookmarkAllTabs.label;"
                          accesskey="&bookmarkAllTabs.accesskey;"
                          command="Browser:BookmarkAllTabs"/>
            <xul:menuitem id="context_undoCloseTab"
                          label="&undoCloseTab.label;"
                          accesskey="&undoCloseTab.accesskey;"
                          command="History:UndoCloseTab"
                          anonid="undoCloseTabMenuItem"/>
            <xul:menuseparator/>
            <xul:menuitem id="context_closeTab" label="&closeTab.label;" accesskey="&closeTab.accesskey;"
                          tbattr="tabbrowser-multiple"
                          oncommand="var tabbrowser = this.parentNode.parentNode.parentNode.parentNode;
                                     tabbrowser.removeTab(tabbrowser.mContextTab);"/>
          </xul:menupopup>

          <xul:tabs class="tabbrowser-tabs" flex="1"
                    anonid="tabcontainer"
                    setfocus="false"
                    onclick="this.parentNode.parentNode.parentNode.onTabClick(event);"
                    ondblclick="this.parentNode.parentNode.parentNode.onTabBarDblClick(event);"
                    onclosetab="var node = this.parentNode;
                                while (node.localName != 'tabbrowser')
                                  node = node.parentNode;
                                node.removeCurrentTab();">
            <xul:tab selected="true" validate="never"
                     onerror="this.removeAttribute('image');"
                     maxwidth="250" width="0" minwidth="100" flex="100"
                     class="tabbrowser-tab" label="&untitledTab;" crop="end"/>
          </xul:tabs>
        </xul:hbox>
        <xul:tabpanels flex="1" class="plain" selectedIndex="0" anonid="panelcontainer">
          <xul:notificationbox flex="1">
            <xul:browser flex="1" type="content-primary" message="true" disablehistory="true"
                         xbl:inherits="tooltip=contenttooltip,contextmenu=contentcontextmenu,autocompletepopup"/>
          </xul:notificationbox>
        </xul:tabpanels>
      </xul:tabbox>
      <children/>
    </content>
    <implementation>
      <field name="mPrefs" readonly="true">
        Components.classes['@mozilla.org/preferences-service;1']
                  .getService(Components.interfaces.nsIPrefService)
                  .getBranch(null);
      </field>
      <field name="mURIFixup" readonly="true">
        Components.classes["@mozilla.org/docshell/urifixup;1"]
                  .getService(Components.interfaces.nsIURIFixup);
      </field>
      <field name="mFaviconService" readonly="true">
        Components.classes["@mozilla.org/browser/favicon-service;1"]
                  .getService(Components.interfaces.nsIFaviconService);
      </field>
      <field name="mTabBox" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "tabbox");
      </field>
      <field name="mTabDropIndicatorBar">
        this.mTabBox.childNodes[0]
      </field>
      <field name="mStrip" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "strip");
      </field>
      <field name="mTabContainer" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "tabcontainer");
      </field>
      <field name="mPanelContainer" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "panelcontainer");
      </field>
      <field name="mTabs" readonly="true">
        this.mTabContainer.childNodes
      </field>
      <field name="mStringBundle">
        document.getAnonymousElementByAttribute(this, "anonid", "tbstringbundle");
      </field>
      <field name="mUndoCloseTabMenuItem">
        document.getAnonymousElementByAttribute(this, "anonid", "undoCloseTabMenuItem");
      </field>
      <field name="mCurrentTab">
        null
      </field>
      <field name="_lastRelatedTab">
        null
      </field>
      <field name="mCurrentBrowser">
        null
      </field>
      <field name="mProgressListeners">
        []
      </field>
      <field name="mTabsProgressListeners">
        []
      </field>
      <field name="mTabListeners">
        new Array()
      </field>
      <field name="mTabFilters">
        new Array()
      </field>
      <field name="mTabbedMode">
        false
      </field>
      <field name="mIsBusy">
        false
      </field>
      <field name="mContextTab">
        null
      </field>
      <field name="arrowKeysShouldWrap" readonly="true">
#ifdef XP_MACOSX
        true
#else
        false
#endif
      </field>
      <field name="mAddProgressListenerWasCalled">
        false
      </field>
      <field name="_browsers">
        null
      </field>

      <field name="_blockDblClick">
        false
      </field>
      <field name="_autoScrollPopup">
        null
      </field>

      <field name="_previewMode">
        false
      </field>

      <method name="previewTab">
        <parameter name="aTab"/>
        <parameter name="aCallback"/>
        <body>
          <![CDATA[
            let currentTab = this.selectedTab;
            try {
              // Suppress focus, ownership and selected tab changes
              this._previewMode = true;
              this.selectedTab = aTab;
              aCallback();
            } finally {
              this.selectedTab = currentTab;
              this._previewMode = false;
            }
          ]]>
        </body>
      </method>

      <method name="getBrowserAtIndex">
        <parameter name="aIndex"/>
        <body>
          <![CDATA[
            return this.browsers[aIndex];
          ]]>
        </body>
      </method>

      <method name="getBrowserIndexForDocument">
        <parameter name="aDocument"/>
        <body>
          <![CDATA[
            var browsers = this.browsers;
            for (var i = 0; i < browsers.length; i++)
              if (browsers[i].contentDocument == aDocument)
                return i;
            return -1;
          ]]>
        </body>
      </method>

      <method name="getBrowserForDocument">
        <parameter name="aDocument"/>
        <body>
          <![CDATA[
            var index = this.getBrowserIndexForDocument(aDocument);
            if (index < 0)
              return null;
            return this.getBrowserAtIndex(index);
          ]]>
        </body>
      </method>

      <method name="getNotificationBox">
        <parameter name="aBrowser"/>
        <body>
          <![CDATA[
            if (aBrowser)
              return aBrowser.parentNode;
            else if (this.mCurrentBrowser)
              return this.mCurrentBrowser.parentNode;
            return null;
          ]]>
        </body>
      </method>

      <!-- A web progress listener object definition for a given tab. -->
      <method name="mTabProgressListener">
        <parameter name="aTab"/>
        <parameter name="aBrowser"/>
        <parameter name="aStartsBlank"/>
        <body>
        <![CDATA[
          return ({
            mTabBrowser: this,
            mTab: aTab,
            mBrowser: aBrowser,
            mBlank: aStartsBlank,

            // cache flags for correct status bar update after tab switching
            mStateFlags: 0,
            mStatus: 0,
            mMessage: "",
            mTotalProgress: 0,

            // count of open requests (should always be 0 or 1)
            mRequestCount: 0,

            destroy: function () {
              this._cancelStalledTimer();
              this.mTab.removeAttribute("stalled");
              delete this.mTab;
              delete this.mBrowser;
              delete this.mTabBrowser;
            },

            onProgressChange : function (aWebProgress, aRequest,
                                         aCurSelfProgress, aMaxSelfProgress,
                                         aCurTotalProgress, aMaxTotalProgress)
            {
              this.mTotalProgress = aMaxTotalProgress ? aCurTotalProgress / aMaxTotalProgress : 0;

              if (this.mBlank)
                return;

              if (this.mTotalProgress) {
                const STATES = 8;
                let state = Math.ceil(STATES * this.mTotalProgress);
                if (state != this.mTab.getAttribute("progress")) {
                  this.mTab.setAttribute("progress", state);
                  this.mTab.removeAttribute("stalled");
                  this._startStalledTimer();
                }
              }

              if (this.mTabBrowser.mCurrentTab == this.mTab) {
                for (let i = 0; i < this.mTabBrowser.mProgressListeners.length; i++) {
                  let p = this.mTabBrowser.mProgressListeners[i];
                  if (p)
                    try {
                      p.onProgressChange(aWebProgress, aRequest,
                                         aCurSelfProgress, aMaxSelfProgress,
                                         aCurTotalProgress, aMaxTotalProgress);
                    } catch (e) {
                      // don't inhibit other listeners
                      Components.utils.reportError(e);
                    }
                }
              }

              for (let i = 0; i < this.mTabBrowser.mTabsProgressListeners.length; i++) {
                let p = this.mTabBrowser.mTabsProgressListeners[i];
                if (p)
                  try {
                    p.onProgressChange(this.mBrowser, aWebProgress, aRequest,
                                       aCurSelfProgress, aMaxSelfProgress,
                                       aCurTotalProgress, aMaxTotalProgress);
                  } catch (e) {
                    // don't inhibit other listeners
                    Components.utils.reportError(e);
                  }
              }
            },

            onProgressChange64 : function (aWebProgress, aRequest,
                                         aCurSelfProgress, aMaxSelfProgress,
                                         aCurTotalProgress, aMaxTotalProgress)
            {
              return this.onProgressChange(aWebProgress, aRequest,
                aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress,
                aMaxTotalProgress);
            },

            onStateChange : function(aWebProgress, aRequest, aStateFlags, aStatus)
            {
              if (!aRequest)
                return;

              var oldBlank = this.mBlank;

              const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
              const nsIChannel = Components.interfaces.nsIChannel;

              if (aStateFlags & nsIWebProgressListener.STATE_START) {
                this.mRequestCount++;
              }
              else if (aStateFlags & nsIWebProgressListener.STATE_STOP) {
                const NS_ERROR_UNKNOWN_HOST = 2152398878;
                if (--this.mRequestCount > 0 && aStatus == NS_ERROR_UNKNOWN_HOST) {
                  // to prevent bug 235825: wait for the request handled
                  // by the automatic keyword resolver
                  return;
                }
                // since we (try to) only handle STATE_STOP of the last request,
                // the count of open requests should now be 0
                this.mRequestCount = 0;
              }

              if (aStateFlags & nsIWebProgressListener.STATE_START &&
                  aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
                // It's okay to clear what the user typed when we start
                // loading a document. If the user types, this counter gets
                // set to zero, if the document load ends without an
                // onLocationChange, this counter gets decremented
                // (so we keep it while switching tabs after failed loads)
                // We need to add 2 because loadURIWithFlags may have
                // cancelled a pending load which would have cleared
                // its anchor scroll detection temporary increment.
                if (aWebProgress.DOMWindow == this.mBrowser.contentWindow)
                  this.mBrowser.userTypedClear += 2;

                if (!this.mBlank) {
                  if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) {
                    this.mTab.setAttribute("busy", "true");
                    this._startStalledTimer();
                    this.mTabBrowser.updateIcon(this.mTab);
                    this.mTabBrowser.setTabTitleLoading(this.mTab);
                  }

                  if (this.mTabBrowser.mCurrentTab == this.mTab)
                    this.mTabBrowser.mIsBusy = true;
                }
              }
              else if (aStateFlags & nsIWebProgressListener.STATE_STOP &&
                       aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
                if (aWebProgress.DOMWindow == this.mBrowser.contentWindow) {
                  // The document is done loading, we no longer want the
                  // value cleared.
                  if (this.mBrowser.userTypedClear > 1)
                    this.mBrowser.userTypedClear -= 2;
                  else if (this.mBrowser.userTypedClear > 0)
                    this.mBrowser.userTypedClear--;

                  if (!this.mBrowser.mIconURL)
                    this.mTabBrowser.useDefaultIcon(this.mTab);
                }

                if (this.mBlank)
                  this.mBlank = false;

                this.mTab.removeAttribute("busy");
                this.mTab.removeAttribute("progress");
                this.mTab.removeAttribute("stalled");
                this._cancelStalledTimer();
                this.mTabBrowser.updateIcon(this.mTab);

                var location = aRequest.QueryInterface(nsIChannel).URI;

                // For keyword URIs clear the user typed value since they will be changed into real URIs
                if (location.scheme == "keyword")
                  this.mBrowser.userTypedValue = null;

                if (this.mTab.label == this.mTabBrowser.mStringBundle.getString("tabs.loading"))
                  this.mTabBrowser.setTabTitle(this.mTab);

                if (this.mTabBrowser.mCurrentTab == this.mTab)
                  this.mTabBrowser.mIsBusy = false;
              }

              if (this.mTabBrowser.mCurrentTab == this.mTab) {
                for (let i = 0; i < this.mTabBrowser.mProgressListeners.length; i++) {
                  let p = this.mTabBrowser.mProgressListeners[i];
                  if (p)
                    try {
                      if (!oldBlank)
                        p.onStateChange(aWebProgress, aRequest, aStateFlags, aStatus);
                      // make sure that the visible status of new blank tabs is correctly set
                      else if ("onUpdateCurrentBrowser" in p)
                        p.onUpdateCurrentBrowser(aStateFlags, aStatus, "", 0);
                    } catch (e) {
                      // don't inhibit other listeners
                      Components.utils.reportError(e);
                    }
                }
              }

              for (let i = 0; i < this.mTabBrowser.mTabsProgressListeners.length; i++) {
                let p = this.mTabBrowser.mTabsProgressListeners[i];
                if (p)
                  try {
                    p.onStateChange(this.mBrowser, aWebProgress, aRequest, aStateFlags, aStatus);
                  } catch (e) {
                    // don't inhibit other listeners
                    Components.utils.reportError(e);
                  }
              }

              if (aStateFlags & (nsIWebProgressListener.STATE_START |
                                 nsIWebProgressListener.STATE_STOP)) {
                // reset cached temporary values at beginning and end
                this.mMessage = "";
                this.mTotalProgress = 0;
              }
              this.mStateFlags = aStateFlags;
              this.mStatus = aStatus;
            },

            onLocationChange : function(aWebProgress, aRequest, aLocation)
            {
              // The document loaded correctly, clear the value if we should
              if (this.mBrowser.userTypedClear > 0)
                this.mBrowser.userTypedValue = null;

              if (aWebProgress.DOMWindow == this.mBrowser.contentWindow &&
                  aWebProgress.isLoadingDocument)
                this.mTabBrowser.getBrowserForTab(this.mTab).mIconURL = null;

              // changing location, clear out the missing plugins list
              this.mBrowser.missingPlugins = null;

              if (this.mBlank)
                return;

              if (this.mTabBrowser.mCurrentTab == this.mTab) {
                for (let i = 0; i < this.mTabBrowser.mProgressListeners.length; i++) {
                  let p = this.mTabBrowser.mProgressListeners[i];
                  if (p)
                    try {
                      p.onLocationChange(aWebProgress, aRequest, aLocation);
                    } catch (e) {
                      // don't inhibit other listeners
                      Components.utils.reportError(e);
                    }
                }
              }

              for (let i = 0; i < this.mTabBrowser.mTabsProgressListeners.length; i++) {
                let p = this.mTabBrowser.mTabsProgressListeners[i];
                if (p)
                  try {
                    p.onLocationChange(this.mBrowser, aWebProgress, aRequest, aLocation);
                  } catch (e) {
                    // don't inhibit other listeners
                    Components.utils.reportError(e);
                  }
              }
            },

            onStatusChange : function(aWebProgress, aRequest, aStatus, aMessage)
            {
              if (this.mBlank)
                return;

              if (this.mTabBrowser.mCurrentTab == this.mTab) {
                for (let i = 0; i < this.mTabBrowser.mProgressListeners.length; i++) {
                  let p = this.mTabBrowser.mProgressListeners[i];
                  if (p)
                    try {
                      p.onStatusChange(aWebProgress, aRequest, aStatus, aMessage);
                    } catch (e) {
                      // don't inhibit other listeners
                      Components.utils.reportError(e);
                    }
                }
              }

              for (let i = 0; i < this.mTabBrowser.mTabsProgressListeners.length; i++) {
                let p = this.mTabBrowser.mTabsProgressListeners[i];
                if (p)
                  try {
                    p.onStatusChange(this.mBrowser, aWebProgress, aRequest, aStatus, aMessage);
                  } catch (e) {
                    // don't inhibit other listeners
                    Components.utils.reportError(e);
                  }
              }

              this.mMessage = aMessage;
            },

            onSecurityChange : function(aWebProgress, aRequest, aState)
            {
              if (this.mTabBrowser.mCurrentTab == this.mTab) {
                for (let i = 0; i < this.mTabBrowser.mProgressListeners.length; i++) {
                  let p = this.mTabBrowser.mProgressListeners[i];
                  if (p)
                    try {
                      p.onSecurityChange(aWebProgress, aRequest, aState);
                    } catch (e) {
                      // don't inhibit other listeners
                      Components.utils.reportError(e);
                    }
                }
              }

              for (let i = 0; i < this.mTabBrowser.mTabsProgressListeners.length; i++) {
                let p = this.mTabBrowser.mTabsProgressListeners[i];
                if (p)
                  try {
                    p.onSecurityChange(this.mBrowser, aWebProgress, aRequest, aState);
                  } catch (e) {
                    // don't inhibit other listeners
                    Components.utils.reportError(e);
                  }
              }
            },

            onRefreshAttempted : function(aWebProgress, aURI, aDelay, aSameURI)
            {
              var allowRefresh = true;
              if (this.mTabBrowser.mCurrentTab == this.mTab) {
                for (let i = 0; i < this.mTabBrowser.mProgressListeners.length; i++) {
                  let p = this.mTabBrowser.mProgressListeners[i];
                  if (p && "onRefreshAttempted" in p) {
                    try {
                      if (!p.onRefreshAttempted(aWebProgress, aURI, aDelay, aSameURI))
                        allowRefresh = false;
                     } catch (e) {
                       // don't inhibit other listeners
                       Components.utils.reportError(e);
                     }
                  }
                }
              }

              for (let i = 0; i < this.mTabBrowser.mTabsProgressListeners.length; i++) {
                let p = this.mTabBrowser.mTabsProgressListeners[i];
                if (p && "onRefreshAttempted" in p) {
                  try {
                    if (!p.onRefreshAttempted(this.mBrowser, aWebProgress, aURI, aDelay, aSameURI))
                      allowRefresh = false;
                   } catch (e) {
                     // don't inhibit other listeners
                     Components.utils.reportError(e);
                   }
                }
              }
              return allowRefresh;
            },

            QueryInterface : function(aIID)
            {
              if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
                  aIID.equals(Components.interfaces.nsIWebProgressListener2) ||
                  aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
                  aIID.equals(Components.interfaces.nsISupports))
                return this;
              throw Components.results.NS_NOINTERFACE;
            },

            _startStalledTimer: function () {
              this._cancelStalledTimer();
              this._stalledTimer = setTimeout(function (self) { 
                self.mTab.setAttribute("stalled", "true");
              }, 700, this);
            },

            _cancelStalledTimer: function () {
              if (this._stalledTimer) {
                clearTimeout(this._stalledTimer);
                this._stalledTimer = 0;
              }
            }
          });
        ]]>
        </body>
      </method>

      <method name="setIcon">
        <parameter name="aTab"/>
        <parameter name="aURI"/>
        <body>
          <![CDATA[
            var browser = this.getBrowserForTab(aTab);
            browser.mIconURL = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;

            if (aURI && this.mFaviconService) {
              if (!(aURI instanceof Ci.nsIURI))
                aURI = makeURI(aURI);
              this.mFaviconService.setAndLoadFaviconForPage(browser.currentURI,
                                                            aURI, false);
            }

            this.updateIcon(aTab);

            if (browser == this.mCurrentBrowser) {
              for (let i = 0; i < this.mProgressListeners.length; i++) {
                let p = this.mProgressListeners[i];
                if ('onLinkIconAvailable' in p)
                  try {
                    p.onLinkIconAvailable(browser, browser.mIconURL);
                  } catch (e) {
                    // don't inhibit other listeners
                    Components.utils.reportError(e);
                  }
              }
            }

            for (let i = 0; i < this.mTabsProgressListeners.length; i++) {
              let p = this.mTabsProgressListeners[i];
              if ('onLinkIconAvailable' in p)
                try {
                  p.onLinkIconAvailable(browser, browser.mIconURL);
                } catch (e) {
                  // don't inhibit other listeners
                  Components.utils.reportError(e);
                }
            }
          ]]>
        </body>
      </method>

      <method name="getIcon">
        <parameter name="aTab"/>
        <body>
          <![CDATA[
            let browser = aTab ? this.getBrowserForTab(aTab) : this.selectedBrowser;
            return browser.mIconURL;
          ]]>
        </body>
      </method>

      <method name="updateIcon">
        <parameter name="aTab"/>
        <body>
          <![CDATA[
            var browser = this.getBrowserForTab(aTab);
            if (!aTab.hasAttribute("busy") && browser.mIconURL)
              aTab.setAttribute("image", browser.mIconURL);
            else
              aTab.removeAttribute("image");
            this._tabAttrModified(aTab);
          ]]>
        </body>
      </method>

      <method name="shouldLoadFavIcon">
        <parameter name="aURI"/>
        <body>
          <![CDATA[
            return (aURI && this.mPrefs.getBoolPref("browser.chrome.site_icons") &&
                    this.mPrefs.getBoolPref("browser.chrome.favicons") &&
                    ("schemeIs" in aURI) && (aURI.schemeIs("http") || aURI.schemeIs("https")));
          ]]>
        </body>
      </method>

      <method name="useDefaultIcon">
        <parameter name="aTab"/>
        <body>
          <![CDATA[
            var browser = this.getBrowserForTab(aTab);
            var docURIObject = browser.contentDocument.documentURIObject; 
            if (browser.contentDocument instanceof ImageDocument) {
              if (this.mPrefs.getBoolPref("browser.chrome.site_icons")) {
                try {
                  var sz = this.mPrefs.getIntPref("browser.chrome.image_icons.max_size");
                  if (!sz)
                    return;

                  var req = browser.contentDocument.imageRequest;
                  if (!req || !req.image ||
                      req.image.width > sz ||
                      req.image.height > sz)
                    return;

                  this.setIcon(aTab, browser.currentURI);
                } catch (e) { }
              }
            }
            // Use documentURIObject in the check for shouldLoadFavIcon so that we
            // do the right thing with about:-style error pages.  Bug 453442
            else if (this.shouldLoadFavIcon(docURIObject)) {
              var url = docURIObject.prePath + "/favicon.ico";
              if (!this.isFailedIcon(url))
                this.setIcon(aTab, url);
            }
          ]]>
        </body>
      </method>

      <method name="isFailedIcon">
        <parameter name="aURI"/>
        <body>
          <![CDATA[
            if (this.mFaviconService) {
              if (!(aURI instanceof Ci.nsIURI))
                aURI = makeURI(aURI);
              return this.mFaviconService.isFailedFavicon(aURI);
            }
            return null;
          ]]>
        </body>
      </method>

      <method name="updateTitlebar">
        <body>
          <![CDATA[
            var newTitle = "";
            var docTitle;
            var docElement = this.ownerDocument.documentElement;
            var sep = docElement.getAttribute("titlemenuseparator");

            if (this.docShell.contentViewer)
              docTitle = this.contentTitle;

            if (!docTitle)
              docTitle = docElement.getAttribute("titledefault");

            var modifier = docElement.getAttribute("titlemodifier");
            if (docTitle) {
              newTitle += docElement.getAttribute("titlepreface");
              newTitle += docTitle;
              if (modifier)
                newTitle += sep;
            }
            newTitle += modifier;

            // If location bar is hidden and the URL type supports a host,
            // add the scheme and host to the title to prevent spoofing.
            // XXX https://bugzilla.mozilla.org/show_bug.cgi?id=22183#c239
            try {
              if (docElement.getAttribute("chromehidden").indexOf("location") != -1) {
                var uri = this.mURIFixup.createExposableURI(
                            this.mCurrentBrowser.currentURI);
                if (uri.scheme == "about")
                  newTitle = uri.spec + sep + newTitle;
                else
                  newTitle = uri.prePath + sep + newTitle;
              }
            } catch (e) {}

            this.ownerDocument.title = newTitle;
          ]]>
        </body>
      </method>

      <method name="updatePopupMenu">
        <parameter name="aPopupMenu"/>
        <body>
          <![CDATA[
            this.mContextTab = document.popupNode.localName == "tab" ?
                               document.popupNode : this.selectedTab;
            var disabled = this.mTabs.length == 1;
            var menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-multiple");
            for (var i = 0; i < menuItems.length; i++)
              menuItems[i].disabled = disabled;

            // Session store
            // XXXzeniko should't we just disable this item as we disable
            // the tabbrowser-multiple items above - for consistency?
            this.mUndoCloseTabMenuItem.hidden =
              Cc["@mozilla.org/browser/sessionstore;1"].
              getService(Ci.nsISessionStore).
              getClosedTabCount(window) == 0;
          ]]>
        </body>
      </method>

      <method name="updateCurrentBrowser">
        <parameter name="aForceUpdate"/>
        <body>
          <![CDATA[
            var newBrowser = this.getBrowserAtIndex(this.mTabContainer.selectedIndex);
            if (this.mCurrentBrowser == newBrowser && !aForceUpdate)
              return;

            var oldTab = this.mCurrentTab;

            // Preview mode should not reset the owner
            if (!this._previewMode && oldTab != this.selectedTab)
              oldTab.owner = null;

            this._lastRelatedTab = null;

            var oldBrowser = this.mCurrentBrowser;
            if (oldBrowser)
              oldBrowser.setAttribute("type", "content-targetable");

            var updatePageReport = false;
            if (!oldBrowser ||
                (oldBrowser.pageReport && !newBrowser.pageReport) ||
                (!oldBrowser.pageReport && newBrowser.pageReport))
              updatePageReport = true;

            newBrowser.setAttribute("type", "content-primary");
            this.mCurrentBrowser = newBrowser;
            this.mCurrentTab = this.selectedTab;

            if (updatePageReport)
              this.mCurrentBrowser.updatePageReport();

            // Update the URL bar.
            var loc = this.mCurrentBrowser.currentURI;

            var webProgress = this.mCurrentBrowser.webProgress;
            var securityUI = this.mCurrentBrowser.securityUI;

            var i, p;
            for (i = 0; i < this.mProgressListeners.length; i++) {
              p = this.mProgressListeners[i];
              if (p)
                try {
                  p.onLocationChange(webProgress, null, loc);
                  if (securityUI)
                    p.onSecurityChange(webProgress, null, securityUI.state);

                  // make sure that all status indicators are properly updated
                  if ("onUpdateCurrentBrowser" in p) {
                    var listener = this.mTabListeners[this.mTabContainer.selectedIndex] || null;
                    if (listener && listener.mStateFlags)
                      p.onUpdateCurrentBrowser(listener.mStateFlags, listener.mStatus,
                                               listener.mMessage, listener.mTotalProgress);
                  }
                } catch (e) {
                  // don't inhibit other listeners or following code
                  Components.utils.reportError(e);
                }
            }

            // Don't switch the fast find - this tab switch is temporary
            if (!this._previewMode)
              this._fastFind.setDocShell(this.mCurrentBrowser.docShell);

            // Update the window title.
            this.updateTitlebar();

            // If the new tab is busy, and our current state is not busy, then
            // we need to fire a start to all progress listeners.
            const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
            if (this.mCurrentTab.hasAttribute("busy") && !this.mIsBusy) {
              this.mIsBusy = true;
              for (i = 0; i < this.mProgressListeners.length; i++) {
                p = this.mProgressListeners[i];
                if (p)
                  try {
                    p.onStateChange(webProgress, null, nsIWebProgressListener.STATE_START | nsIWebProgressListener.STATE_IS_NETWORK, 0);
                  } catch (e) {
                    // don't inhibit other listeners or following code
                    Components.utils.reportError(e);
                  }
              }
            }

            // If the new tab is not busy, and our current state is busy, then
            // we need to fire a stop to all progress listeners.
            if (!this.mCurrentTab.hasAttribute("busy") && this.mIsBusy) {
              this.mIsBusy = false;
              for (i = 0; i < this.mProgressListeners.length; i++) {
                p = this.mProgressListeners[i];
                if (p)
                  try {
                    p.onStateChange(webProgress, null, nsIWebProgressListener.STATE_STOP | nsIWebProgressListener.STATE_IS_NETWORK, 0);
                  } catch (e) {
                    // don't inhibit other listeners or following code
                    Components.utils.reportError(e);
                  }
              }
            }

            // TabSelect events are suppressed during preview mode to avoid confusing extensions and other bits of code
            // that might rely upon the other changes suppressed.
            // Focus is suppressed in the event that the main browser window is minimized - focusing a tab would restore the window
            if (!this._previewMode) {
              // We've selected the new tab, so go ahead and notify listeners.
              var event = document.createEvent("Events");
              event.initEvent("TabSelect", true, false);
              this.mCurrentTab.dispatchEvent(event);

              this._tabAttrModified(oldTab);
              this._tabAttrModified(this.mCurrentTab);

              // Change focus to the new browser unless the findbar is focused.
              if (gFindBar.hidden ||
                  gFindBar.getElement("findbar-textbox").getAttribute("focused") != "true")
                newBrowser.focus();
            }
          ]]>
        </body>
      </method>

      <method name="_tabAttrModified">
        <parameter name="aTab"/>
        <body><![CDATA[
          // This event should be dispatched when any of these attributes change:
          // label, crop, busy, image, selected
          var event = document.createEvent("Events");
          event.initEvent("TabAttrModified", true, false);
          aTab.dispatchEvent(event);
        ]]></body>
      </method>

      <method name="onTabClick">
        <parameter name="event"/>
        <body>
          <![CDATA[
            if (event.button != 1 || event.target.localName != 'tab')
              return;

            if (this.mTabs.length > 1 ||
                !this.mPrefs.getBoolPref("browser.tabs.closeWindowWithLastTab"))
              this.removeTab(event.target);

            event.stopPropagation();
          ]]>
        </body>
      </method>

      <method name="onTitleChanged">
        <parameter name="evt"/>
        <body>
          <![CDATA[
            if (evt.target != this.contentDocument)
              return;

            var tabBrowser = this.parentNode.parentNode.parentNode.parentNode;

            var tab = document.getAnonymousElementByAttribute(tabBrowser, "linkedpanel", this.parentNode.id);
            tabBrowser.setTabTitle(tab);

            if (tab == tabBrowser.mCurrentTab)
              tabBrowser.updateTitlebar();
          ]]>
        </body>
      </method>

      <method name="setTabTitleLoading">
        <parameter name="aTab"/>
        <body>
          <![CDATA[
            aTab.label = this.mStringBundle.getString("tabs.loading");
            aTab.setAttribute("crop", "end");
            this._tabAttrModified(aTab);
          ]]>
        </body>
      </method>

      <method name="setTabTitle">
        <parameter name="aTab"/>
        <body>
          <![CDATA[
            var browser = this.getBrowserForTab(aTab);
            var crop = "end";
            var title = browser.contentTitle;

            if (!title) {
              if (browser.currentURI.spec) {
                try {
                  title = this.mURIFixup.createExposableURI(browser.currentURI).spec;
                } catch(ex) {
                  title = browser.currentURI.spec;
                }
              }

              if (title && title != "about:blank") {
                // At this point, we now have a URI.
                // Let's try to unescape it using a character set
                // in case the URI is not ASCII.
                try {
                  var characterSet = browser.contentDocument.characterSet;
                  const textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"]
                                                 .getService(Components.interfaces.nsITextToSubURI);
                  title = textToSubURI.unEscapeNonAsciiURI(characterSet, title);
                } catch(ex) { /* Do nothing. */ }

                crop = "center";

              } else // Still no title?  Fall back to our untitled string.
                title = this.mStringBundle.getString("tabs.untitled");
            }

            aTab.label = title;
            aTab.setAttribute("crop", crop);
            this._tabAttrModified(aTab);
          ]]>
        </body>
      </method>

      <method name="setStripVisibilityTo">
        <parameter name="aShow"/>
        <body>
        <![CDATA[
          this.mStrip.collapsed = !aShow;
          if (aShow) {
            // XXXdwh temporary unclean dependency on specific menu items in navigator.xul
            document.getElementById("menu_closeWindow").hidden = false;
            document.getElementById("menu_close").setAttribute("label", this.mStringBundle.getString("tabs.closeTab"));
            if (!this.mTabbedMode)
              this.enterTabbedMode();
          }
          else {
            // XXXdwh temporary unclean dependency on specific menu items in navigator.xul
            document.getElementById("menu_closeWindow").hidden = true;
            document.getElementById("menu_close").setAttribute("label", this.mStringBundle.getString("tabs.close"));
          }
        ]]>
        </body>
      </method>

      <method name="getStripVisibility">
        <body>
          return !this.mStrip.collapsed;
        </body>
      </method>

      <method name="enterTabbedMode">
        <body>
          <![CDATA[
            this.mTabbedMode = true; // Welcome to multi-tabbed mode.

            // Get the first tab all hooked up with a title listener and popup blocking listener.
            this.mCurrentBrowser.addEventListener("DOMTitleChanged", this.onTitleChanged, true);

            if (XULBrowserWindow.isBusy) {
              this.mCurrentTab.setAttribute("busy", "true");
              this.mIsBusy = true;
              this.setTabTitleLoading(this.mCurrentTab);
              this.updateIcon(this.mCurrentTab);
            } else {
              this.setTabTitle(this.mCurrentTab);
              this.setIcon(this.mCurrentTab, this.mCurrentBrowser.mIconURL);
            }

            var filter;
            if (this.mTabFilters.length > 0) {
              // Use the filter hooked up in our addProgressListener
              filter = this.mTabFilters[0];
            } else {
              // create a filter and hook it up to our first browser
              filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
                                 .createInstance(Components.interfaces.nsIWebProgress);
              this.mTabFilters[0] = filter;
              this.mCurrentBrowser.webProgress.addProgressListener(filter, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
            }

            // Remove all our progress listeners from the active browser's filter.
            for (var i = 0; i < this.mProgressListeners.length; i++) {
              var p = this.mProgressListeners[i];
              if (p)
                filter.removeProgressListener(p);
            }

            // Wire up a progress listener to our filter.
            const listener = this.mTabProgressListener(this.mCurrentTab,
                                                       this.mCurrentBrowser,
                                                       false);
            filter.addProgressListener(listener, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
            this.mTabListeners[0] = listener;
          ]]>
        </body>
      </method>

      <method name="loadOneTab">
        <parameter name="aURI"/>
        <parameter name="aReferrerURI"/>
        <parameter name="aCharset"/>
        <parameter name="aPostData"/>
        <parameter name="aLoadInBackground"/>
        <parameter name="aAllowThirdPartyFixup"/>
        <body>
          <![CDATA[
            var aRelatedToCurrent;
            if (arguments.length == 2 &&
                typeof arguments[1] == "object" &&
                !(arguments[1] instanceof Ci.nsIURI)) {
              let params = arguments[1];
              aReferrerURI          = params.referrerURI;
              aCharset              = params.charset;
              aPostData             = params.postData;
              aLoadInBackground     = params.inBackground;
              aAllowThirdPartyFixup = params.allowThirdPartyFixup;
              aRelatedToCurrent     = params.relatedToCurrent;
            }

            var bgLoad = (aLoadInBackground != null) ? aLoadInBackground :
                         this.mPrefs.getBoolPref("browser.tabs.loadInBackground");
            var owner = bgLoad ? null : this.selectedTab;
            var tab = this.addTab(aURI, {
                                  referrerURI: aReferrerURI,
                                  charset: aCharset,
                                  postData: aPostData,
                                  ownerTab: owner,
                                  allowThirdPartyFixup: aAllowThirdPartyFixup,
                                  relatedToCurrent: aRelatedToCurrent});
            if (!bgLoad)
              this.selectedTab = tab;

            return tab;
         ]]>
        </body>
      </method>

      <method name="loadTabs">
        <parameter name="aURIs"/>
        <parameter name="aLoadInBackground"/>
        <parameter name="aReplace"/>
        <body><![CDATA[
          if (!aURIs.length)
            return;

          // The tab selected after this new tab is closed (i.e. the new tab's
          // "owner") is the next adjacent tab (i.e. not the previously viewed tab)
          // when several urls are opened here (i.e. closing the first should select
          // the next of many URLs opened) or if the pref to have UI links opened in
          // the background is set (i.e. the link is not being opened modally)
          //
          // i.e.
          //    Number of URLs    Load UI Links in BG       Focus Last Viewed?
          //    == 1              false                     YES
          //    == 1              true                      NO
          //    > 1               false/true                NO
          var owner = (aURIs.length > 1) || aLoadInBackground ? null : this.selectedTab;
          var firstTabAdded = null;

          if (aReplace) {
            try {
              this.loadURI(aURIs[0], null, null);
            } catch (e) {
              // Ignore failure in case a URI is wrong, so we can continue
              // opening the next ones.
            }
          }
          else
            firstTabAdded = this.addTab(aURIs[0], {ownerTab: owner});

          var tabNum = this.mTabContainer.selectedIndex;
          for (let i = 1; i < aURIs.length; ++i) {
            let tab = this.addTab(aURIs[i]);
            if (aReplace)
              this.moveTabTo(tab, ++tabNum);
          }

          if (!aLoadInBackground) {
            if (firstTabAdded) {
              // .selectedTab setter focuses the content area
              this.selectedTab = firstTabAdded;
            }
            else
              window.content.focus();
          }
        ]]></body>
      </method>

      <method name="addTab">
        <parameter name="aURI"/>
        <parameter name="aReferrerURI"/>
        <parameter name="aCharset"/>
        <parameter name="aPostData"/>
        <parameter name="aOwner"/>
        <parameter name="aAllowThirdPartyFixup"/>
        <body>
          <![CDATA[
            var aRelatedToCurrent;
            if (arguments.length == 2 &&
                typeof arguments[1] == "object" &&
                !(arguments[1] instanceof Ci.nsIURI)) {
              let params = arguments[1];
              aReferrerURI          = params.referrerURI;
              aCharset              = params.charset;
              aPostData             = params.postData;
              aOwner                = params.ownerTab;
              aAllowThirdPartyFixup = params.allowThirdPartyFixup;
              aRelatedToCurrent     = params.relatedToCurrent;
            }

            this._browsers = null; // invalidate cache

            if (!this.mTabbedMode)
              this.enterTabbedMode();

            // if we're adding tabs, we're past interrupt mode, ditch the owner
            if (this.mCurrentTab.owner)
              this.mCurrentTab.owner = null;

            var t = document.createElementNS(
              "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
                                             "tab");

            var blank = !aURI || (aURI == "about:blank");

            if (blank)
              t.setAttribute("label", this.mStringBundle.getString("tabs.untitled"));
            else
              t.setAttribute("label", aURI);

            t.setAttribute("crop", "end");
            t.maxWidth = this.mTabContainer.mTabMaxWidth;
            t.minWidth = this.mTabContainer.mTabMinWidth;
            t.width = 0;
            t.setAttribute("flex", "100");
            t.setAttribute("validate", "never");
            t.setAttribute("onerror", "this.removeAttribute('image');");
            t.className = "tabbrowser-tab";

            this.mTabContainer.appendChild(t);

            if (this.tabContainer.mTabstrip._isRTLScrollbox) {
              /* In RTL UI, the tab is visually added to the left side of the
               * tabstrip. This means the tabstip has to be scrolled back in
               * order to make sure the same set of tabs is visible before and
               * after the new tab is added.  See bug 508816. */

              this.tabContainer.mTabstrip.scrollByPixels(this.mTabs[0].clientWidth);
            }

            // invalidate cache, because mTabContainer is about to change
            this._browsers = null; 

            // If this new tab is owned by another, assert that relationship
            if (aOwner)
              t.owner = aOwner;

            var b = document.createElementNS(
              "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
                                             "browser");
            b.setAttribute("type", "content-targetable");
            b.setAttribute("message", "true");
            b.setAttribute("contextmenu", this.getAttribute("contentcontextmenu"));
            b.setAttribute("tooltip", this.getAttribute("contenttooltip"));
            if (this.hasAttribute("autocompletepopup"))
              b.setAttribute("autocompletepopup", this.getAttribute("autocompletepopup"));
            b.setAttribute("autoscrollpopup", this._autoScrollPopup.id);

            // Add the Message and the Browser to the box
            var notificationbox = document.createElementNS(
                                    "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
                                    "notificationbox");
            notificationbox.setAttribute("flex", "1");
            notificationbox.appendChild(b);
            b.setAttribute("flex", "1");
            this.mPanelContainer.appendChild(notificationbox);

            b.addEventListener("DOMTitleChanged", this.onTitleChanged, true);

            if (this.mStrip.collapsed &&
                this.mTabs.length - this._removingTabs.length > 1)
              this.setStripVisibilityTo(true);

            // wire up a progress listener for the new browser object.
            var position = this.mTabContainer.childNodes.length-1;
            var tabListener = this.mTabProgressListener(t, b, blank);
            const filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
                                     .createInstance(Components.interfaces.nsIWebProgress);
            filter.addProgressListener(tabListener, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
            b.webProgress.addProgressListener(filter, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
            this.mTabListeners[position] = tabListener;
            this.mTabFilters[position] = filter;

            b._fastFind = this.fastFind;

            var uniqueId = "panel" + Date.now() + position;
            this.mPanelContainer.lastChild.id = uniqueId;
            t.linkedPanel = uniqueId;
            t.linkedBrowser = b;
            t._tPos = position;
            if (t.previousSibling.selected)
              t.setAttribute("afterselected", true);

            this.tabContainer.adjustTabstrip();

            // Do this asynchronically, as we don't know yet if the tab
            // will be selected.
            setTimeout(function (aTabContainer) {
              if (aTabContainer.selectedItem != t)
                aTabContainer._notifyBackgroundTab(t);
            }, 0, this.tabContainer);

            // XXXmano: this is a temporary workaround for bug 345399
            // We need to manually update the scroll buttons disabled state
            // if a tab was inserted to the overflow area or removed from it
            // without any scrolling and when the tabbar has already
            // overflowed.
            this.tabContainer.mTabstrip._updateScrollButtonsDisabledState();

            // Dispatch a new tab notification.  We do this once we're
            // entirely done, so that things are in a consistent state
            // even if the event listener opens or closes tabs.
            var evt = document.createEvent("Events");
            evt.initEvent("TabOpen", true, false);
            t.dispatchEvent(evt);

            if (!blank) {
              // Stop the existing about:blank load.  Otherwise, if aURI
              // doesn't stop in-progress loads on its own, we'll get into
              // trouble with multiple parallel loads running at once.
              b.stop();

              // pretend the user typed this so it'll be available till
              // the document successfully loads
              b.userTypedValue = aURI;

              let flags = aAllowThirdPartyFixup ?
                          Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP :
                          Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
              try {
                b.loadURIWithFlags(aURI, flags, aReferrerURI, aCharset, aPostData);
              }
              catch (ex) { }
            }

            // Check if we're opening a tab related to the current tab and
            // move it to after the current tab.
            // aReferrerURI is null or undefined if the tab is opened from
            // an external application or bookmark, i.e. somewhere other
            // than the current tab.
            if ((aRelatedToCurrent == null ? aReferrerURI : aRelatedToCurrent) &&
                this.mPrefs.getBoolPref("browser.tabs.insertRelatedAfterCurrent")) {
              let newTabPos = (this._lastRelatedTab ||
                               this.selectedTab)._tPos + 1;
              this.moveTabTo(t, newTabPos);
              this._lastRelatedTab = t;
            }

            return t;
          ]]>
        </body>
      </method>

      <method name="warnAboutClosingTabs">
      <parameter name="aAll"/>
      <body>
        <![CDATA[
          var numTabs = this.mTabContainer.childNodes.length;
          var reallyClose = true;
          if (numTabs <= 1)
            return reallyClose;

          const pref = "browser.tabs.warnOnClose";
          var shouldPrompt = this.mPrefs.getBoolPref(pref);

          if (shouldPrompt) {
            var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
                                          .getService(Components.interfaces.nsIPromptService);

            //default to true: if it were false, we wouldn't get this far
            var warnOnClose = { value:true };
            var bundle = this.mStringBundle;
            var tabsToClose = numTabs;  //number of tabs to be removed
            if (!aAll)
              --tabsToClose;

            var messageKey = (tabsToClose == 1) ? "tabs.closeWarningOneTab" : "tabs.closeWarningMultipleTabs";
            var closeKey = (tabsToClose == 1) ? "tabs.closeButtonOne" : "tabs.closeButtonMultiple";
            // focus the window before prompting.
            // this will raise any minimized window, which will
            // make it obvious which window the prompt is for and will
            // solve the problem of windows "obscuring" the prompt.
            // see bug #350299 for more details
            window.focus();
            var buttonPressed = promptService.confirmEx(window,
                                                        bundle.getString('tabs.closeWarningTitle'),
                                                        bundle.getFormattedString(messageKey, [tabsToClose]),
                                                        (promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_0)
                                                        + (promptService.BUTTON_TITLE_CANCEL * promptService.BUTTON_POS_1),
                                                        bundle.getString(closeKey),
                                                        null, null,
                                                        bundle.getString('tabs.closeWarningPromptMe'),
                                                        warnOnClose);
            reallyClose = (buttonPressed == 0);
            // don't set the pref unless they press OK and it's false
            if (reallyClose && !warnOnClose.value)
              this.mPrefs.setBoolPref(pref, false);
          }
          return reallyClose;
        ]]>
      </body>
      </method>

      <method name="removeAllTabsBut">
        <parameter name="aTab"/>
        <body>
          <![CDATA[
            if (this.warnAboutClosingTabs(false)) {
              this.selectedTab = aTab;

              for (let i = this.mTabs.length - 1; i >= 0; --i) {
                if (this.mTabs[i] != aTab)
                  this.removeTab(this.mTabs[i]);
              }
            }
          ]]>
        </body>
      </method>

      <method name="removeCurrentTab">
        <body>
          <![CDATA[
            this.removeTab(this.mCurrentTab);
          ]]>
        </body>
      </method>

      <field name="_removingTabs">
        []
      </field>

      <method name="removeTab">
        <parameter name="aTab"/>
        <body>
          <![CDATA[
            this._endRemoveTab(this._beginRemoveTab(aTab, false, null, true));
          ]]>
        </body>
      </method>

      <!-- Tab close requests are ignored if the window is closing anyway,
           e.g. when holding Ctrl+W. -->
      <field name="_windowIsClosing">
        false
      </field>

      <!-- Returns everything that _endRemoveTab needs in an array. -->
      <method name="_beginRemoveTab">
        <parameter name="aTab"/>
        <parameter name="aTabWillBeMoved"/>
        <parameter name="aCloseWindowWithLastTab"/>
        <parameter name="aCloseWindowFastpath"/>
        <body>
          <![CDATA[
            if (this._removingTabs.indexOf(aTab) > -1 || this._windowIsClosing)
              return null;

            var browser = this.getBrowserForTab(aTab);

            if (!aTabWillBeMoved) {
              let ds = browser.docShell;
              if (ds.contentViewer && !ds.contentViewer.permitUnload())
                return null;
            }

            var closeWindow = false;
            var l = this.mTabs.length - this._removingTabs.length;
            var newTab = false;
            if (l == 1) {
              closeWindow = aCloseWindowWithLastTab != null ? aCloseWindowWithLastTab :
                            !window.toolbar.visible ||
                              this.mPrefs.getBoolPref("browser.tabs.closeWindowWithLastTab");

              // Closing the tab and replacing it with a blank one is notably slower
              // than closing the window right away. If the caller opts in, take
              // the fast path.
              if (closeWindow &&
                  aCloseWindowFastpath &&
                  this._removingTabs.length == 0 &&
                  (this._windowIsClosing = window.closeWindow(true)))
                return null;

              newTab = true;
              l++;
            }
            if (l == 2) {
              let autohide = this.mPrefs.getBoolPref("browser.tabs.autoHide");
              let tabStripHide = !window.toolbar.visible;
              if (autohide || tabStripHide)
                this.setStripVisibilityTo(false);
            }

            this._removingTabs.push(aTab);

            // We're committed to closing the tab now.
            // Dispatch a notification.
            // We dispatch it before any teardown so that event listeners can
            // inspect the tab that's about to close.
            var evt = document.createEvent("UIEvent");
            evt.initUIEvent("TabClose", true, false, window, aTabWillBeMoved ? 1 : 0);
            aTab.dispatchEvent(evt);

            // Remove the tab's filter and progress listener.
            const filter = this.mTabFilters[aTab._tPos];
            browser.webProgress.removeProgressListener(filter);
            filter.removeProgressListener(this.mTabListeners[aTab._tPos]);
            this.mTabListeners[aTab._tPos].destroy();

            // Remove our title change and blocking listeners
            browser.removeEventListener("DOMTitleChanged", this.onTitleChanged, true);

            // We are no longer the primary content area.
            browser.setAttribute("type", "content-targetable");

            // Remove this tab as the owner of any other tabs, since it's going away.
            Array.forEach(this.mTabs, function (tab) {
              if ("owner" in tab && tab.owner == aTab)
                // |tab| is a child of the tab we're removing, make it an orphan
                tab.owner = null;
            });

            return [aTab, closeWindow, newTab];
          ]]>
        </body>
      </method>

      <method name="_endRemoveTab">
        <parameter name="args"/>
        <body>
          <![CDATA[
            if (!args)
              return;
            var [aTab, aCloseWindow, aNewTab] = args;

            this._lastRelatedTab = null;

            // update the UI early for responsiveness
            aTab.collapsed = true;
            if (aNewTab)
              this.addTab("about:blank");
            this.tabContainer._fillTrailingGap();
            this._blurTab(aTab);

            this._removingTabs.splice(this._removingTabs.indexOf(aTab), 1);

            if (aCloseWindow) {
              this._windowIsClosing = true;
              while (this._removingTabs.length)
                this._endRemoveTab([this._removingTabs[0], false]);
            } else if (!this._windowIsClosing) {
              if (aNewTab)
                focusAndSelectUrlBar();

              // workaround for bug 345399
              this.tabContainer.mTabstrip._updateScrollButtonsDisabledState();
            }

            // We're going to remove the tab and the browser now.
            // Clean up mTabFilters and mTabListeners now rather than in
            // _beginRemoveTab, so that their size is always in sync with the
            // number of tabs and browsers (the xbl destructor depends on this).
            this.mTabFilters.splice(aTab._tPos, 1);
            this.mTabListeners.splice(aTab._tPos, 1);

            var browser = this.getBrowserForTab(aTab);

            // Because of the way XBL works (fields just set JS
            // properties on the element) and the code we have in place
            // to preserve the JS objects for any elements that have
            // JS properties set on them, the browser element won't be
            // destroyed until the document goes away.  So we force a
            // cleanup ourselves.
            // This has to happen before we remove the child so that the
            // XBL implementation of nsIObserver still works.
            browser.destroy();

            if (browser == this.mCurrentBrowser)
              this.mCurrentBrowser = null;

            // Invalidate browsers cache, as the tab is removed from the
            // tab container.
            this._browsers = null;

            // Remove the tab ...
            this.tabContainer.removeChild(aTab);

            // ... and fix up the _tPos properties immediately.
            for (let i = aTab._tPos; i < this.mTabs.length; i++)
              this.mTabs[i]._tPos = i;

            // update tab close buttons state
            if (!this._windowIsClosing)
              this.tabContainer.adjustTabstrip();

            // update first-tab/last-tab/beforeselected/afterselected attributes
            this.selectedTab._selected = true;

            // This will unload the document. An unload handler could remove
            // dependant tabs, so it's important that the tabbrowser is now in
            // a consistent state (tab removed, tab positions updated, etc.).
            // Also, it's important that another tab has been selected before
            // the panel is removed; otherwise, a random sibling panel can flash.
            this.mPanelContainer.removeChild(browser.parentNode);

            // As the panel is removed, the removal of a dependent document can
            // cause the whole window to close. So at this point, it's possible
            // that the binding is destructed.
            if (this.mTabBox)
              this.mTabBox.selectedPanel = this.getBrowserForTab(this.mCurrentTab).parentNode;

            if (aCloseWindow)
              this._windowIsClosing = closeWindow(true);
          ]]>
        </body>
      </method>

      <method name="_blurTab">
        <parameter name="aTab"/>
        <body>
          <![CDATA[
            if (this.mCurrentTab != aTab)
              return;

            if (aTab.owner &&
                this._removingTabs.indexOf(aTab.owner) == -1 &&
                this.mPrefs.getBoolPref("browser.tabs.selectOwnerOnClose")) {
              this.selectedTab = aTab.owner;
              return;
            }

            var tab = aTab;

            do {
              tab = tab.nextSibling;
            } while (tab && this._removingTabs.indexOf(tab) != -1);

            if (!tab) {
              tab = aTab;

              do {
                tab = tab.previousSibling;
              } while (tab && this._removingTabs.indexOf(tab) != -1);
            }

            this.selectedTab = tab;
          ]]>
        </body>
      </method>

      <method name="swapBrowsersAndCloseOther">
        <parameter name="aOurTab"/>
        <parameter name="aOtherTab"/>
        <body>
          <![CDATA[
            // That's gBrowser for the other window, not the tab's browser!
            var remoteBrowser =
              aOtherTab.ownerDocument.defaultView.getBrowser();

            // First, start teardown of the other browser.  Make sure to not
            // fire the beforeunload event in the process.  Close the other
            // window if this was its last tab.
            var endRemoveArgs = remoteBrowser._beginRemoveTab(aOtherTab, true, true);

            // Unhook our progress listener
            var ourIndex = aOurTab._tPos;
            const filter = this.mTabFilters[ourIndex];
            var tabListener = this.mTabListeners[ourIndex];
            var ourBrowser = this.getBrowserForTab(aOurTab);
            ourBrowser.webProgress.removeProgressListener(filter);
            filter.removeProgressListener(tabListener);
            var tabListenerBlank = tabListener.mBlank;

            // Workarounds for bug 458697
            // Icon might have been set on DOMLinkAdded, don't override that.
            if (!ourBrowser.mIconURL && aOtherTab.linkedBrowser.mIconURL)
              this.setIcon(aOurTab, aOtherTab.linkedBrowser.mIconURL);
            var isBusy = aOtherTab.hasAttribute("busy");
            if (isBusy) {
              aOurTab.setAttribute("busy", "true");
              this._tabAttrModified(aOurTab);
              if (aOurTab == this.selectedTab)
                this.mIsBusy = true;
            }

            // Swap the docshells
            ourBrowser.swapDocShells(aOtherTab.linkedBrowser);

            // Finish tearing down the tab that's going away.
            remoteBrowser._endRemoveTab(endRemoveArgs);

            // Restore the progress listener
            tabListener = this.mTabProgressListener(aOurTab, ourBrowser,
                                                    tabListenerBlank);
            this.mTabListeners[ourIndex] = tabListener;
            filter.addProgressListener(tabListener,
              Components.interfaces.nsIWebProgress.NOTIFY_ALL);

            ourBrowser.webProgress.addProgressListener(filter,
              Components.interfaces.nsIWebProgress.NOTIFY_ALL);

            if (isBusy)
              this.setTabTitleLoading(aOurTab);
            else
              this.setTabTitle(aOurTab);
            this.updateIcon(aOurTab);

            // If the tab was already selected (this happpens in the scenario
            // of replaceTabWithWindow), notify onLocationChange, etc.
            if (aOurTab == this.selectedTab)
              this.updateCurrentBrowser(true);
          ]]>
        </body>
      </method>

      <method name="reloadAllTabs">
        <body>
          <![CDATA[
            var l = this.mPanelContainer.childNodes.length;
            for (var i = 0; i < l; i++) {
              try {
                this.getBrowserAtIndex(i).reload();
              } catch (e) {
                // ignore failure to reload so others will be reloaded
              }
            }
          ]]>
        </body>
      </method>

      <method name="reloadTab">
        <parameter name="aTab"/>
        <body>
          <![CDATA[
            this.getBrowserForTab(aTab).reload();
          ]]>
        </body>
      </method>

      <method name="onTabBarDblClick">
        <parameter name="aEvent"/>
        <body>
          <![CDATA[
            // See hack note in the tabbrowser-close-button binding
            if (!this._blockDblClick && aEvent.button == 0 &&
                aEvent.originalTarget.localName == "box")
              BrowserOpenTab();
          ]]>
        </body>
      </method>

      <method name="addProgressListener">
        <parameter name="aListener"/>
        <parameter name="aMask"/>
        <body>
          <![CDATA[
            if (!this.mAddProgressListenerWasCalled) {
              this.mAddProgressListenerWasCalled = true;
              var autoHide = this.mPrefs.getBoolPref("browser.tabs.autoHide");
              var tabStripHide = !window.toolbar.visible;
              if (!autoHide && !tabStripHide)
                this.setStripVisibilityTo(true);
            }

            if (!this.mTabbedMode && this.mProgressListeners.length == 1) {
              // If we are adding a 2nd progress listener, we need to enter tabbed mode
              // because the browser status filter can only handle one progress listener.
              // In tabbed mode, mTabProgressListener is used which will iterate over all listeners.
              this.enterTabbedMode();
            }

            this.mProgressListeners.push(aListener);

            if (!this.mTabbedMode) {
              // If someone does this:
              // addProgressListener, removeProgressListener, addProgressListener
              // don't create a new filter; reuse the existing filter.
              if (this.mTabFilters.length == 0) {
                // hook a filter up to our first browser
                const filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
                                         .createInstance(Components.interfaces.nsIWebProgress);
                this.mTabFilters[0] = filter;
                this.mCurrentBrowser.webProgress.addProgressListener(filter, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
              }

              // Directly hook the listener up to the filter for better performance
              this.mTabFilters[0].addProgressListener(aListener, aMask);
            }
          ]]>
        </body>
      </method>

      <method name="removeProgressListener">
        <parameter name="aListener"/>
        <body>
          <![CDATA[
            for (var i = 0; i < this.mProgressListeners.length; i++) {
              if (this.mProgressListeners[i] == aListener) {
                this.mProgressListeners.splice(i, 1);
                break;
              }
            }

            if (!this.mTabbedMode)
              // Don't forget to remove it from the filter we hooked it up to
              this.mTabFilters[0].removeProgressListener(aListener);
         ]]>
        </body>
      </method>

      <method name="addTabsProgressListener">
        <parameter name="aListener"/>
        <body>
          if (!this.mTabbedMode)
            this.enterTabbedMode();
          this.mTabsProgressListeners.push(aListener);
        </body>
      </method>

      <method name="removeTabsProgressListener">
        <parameter name="aListener"/>
        <body>
        <![CDATA[
          var pos = this.mTabsProgressListeners.indexOf(aListener);
          if (pos >= 0)
            this.mTabsProgressListeners.splice(pos, 1);
        ]]>
        </body>
      </method>

      <method name="getBrowserForTab">
        <parameter name="aTab"/>
        <body>
        <![CDATA[
          return aTab.linkedBrowser;
        ]]>
        </body>
      </method>

      <method name="selectTabAtIndex">
        <parameter name="aIndex"/>
        <parameter name="aEvent"/>
        <body>
        <![CDATA[
          // count backwards for aIndex < 0
          if (aIndex < 0)
            aIndex += this.mTabs.length;

          if (aIndex >= 0 &&
              aIndex < this.mTabs.length &&
              aIndex != this.tabContainer.selectedIndex)
            this.selectedTab = this.mTabs[aIndex];

          if (aEvent) {
            aEvent.preventDefault();
            aEvent.stopPropagation();
          }
        ]]>
        </body>
      </method>

      <property name="tabContainer" readonly="true">
        <getter>
          return this.mTabContainer;
        </getter>
      </property>

      <property name="selectedTab">
        <getter>
          return this.mTabBox.selectedTab;
        </getter>
        <setter>
          <![CDATA[
          // Update the tab
          this.mTabBox.selectedTab = val;
          return val;
          ]]>
        </setter>
      </property>

      <property name="selectedBrowser"
                onget="return this.mCurrentBrowser;"
                readonly="true"/>

      <property name="browsers" readonly="true">
       <getter>
          <![CDATA[
            return this._browsers ||
                   (this._browsers = Array.map(this.mTabs, function (tab) tab.linkedBrowser));
          ]]>
        </getter>
      </property>

      <method name="_onDragStart">
        <parameter name="aEvent"/>
        <body>
        <![CDATA[
          var target = aEvent.target;
          if (target.localName == "tab" &&
              aEvent.originalTarget.localName != "toolbarbutton") {
            var dt = aEvent.dataTransfer;
            dt.mozSetDataAt(TAB_DROP_TYPE, target, 0);
            var uri = this.getBrowserForTab(aEvent.target).currentURI;
            var spec = uri ? uri.spec : "about:blank";

            // We must not set text/x-moz-url or text/plain data here,
            // otherwise trying to deatch the tab by dropping it on the desktop
            // may result in an "internet shortcut"
            dt.mozSetDataAt("text/x-moz-text-internal", spec, 0);
            
            // Set the cursor to an arrow during tab drags.
            dt.mozCursor = "default";

            var canvas = tabPreviews.capture(target, false);
            dt.setDragImage(canvas, 0, 0);
            aEvent.stopPropagation();
          }

          this._dragLeftWindow = false;
        ]]>
        </body>
      </method>

      <field name="mDragTime">0</field>
      <field name="mDragOverDelay">350</field>

      <field name="_supportedLinkDropTypes"><![CDATA[
        ["text/x-moz-url", "text/uri-list", "text/plain", "application/x-moz-file"]
      ]]></field>

      <method name="_setEffectAllowedForDataTransfer">
        <parameter name="aEvent"/>
        <body>
          <![CDATA[
            var dt = aEvent.dataTransfer;
            // Disallow dropping multiple items
            if (dt.mozItemCount > 1)
              return dt.effectAllowed = "none";

            var types = dt.mozTypesAt(0);
            var sourceNode = null;
            // tabs are always added as the first type
            if (types[0] == TAB_DROP_TYPE) {
              var sourceNode = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
              if (sourceNode instanceof XULElement &&
                  sourceNode.localName == "tab" &&
                  (sourceNode.parentNode == this.mTabContainer ||
                   (sourceNode.ownerDocument.defaultView instanceof ChromeWindow &&
                    sourceNode.ownerDocument.documentElement.getAttribute("windowtype") == "navigator:browser"))) {
                if (sourceNode.parentNode == this.mTabContainer &&
                    (aEvent.screenX >= sourceNode.boxObject.screenX &&
                      aEvent.screenX <= (sourceNode.boxObject.screenX +
                                         sourceNode.boxObject.width))) {
                  return dt.effectAllowed = "none";
                }

                return dt.effectAllowed = "copyMove";
              }
            }

            for (var i=0; i < this._supportedLinkDropTypes.length; i++) {
              if (types.contains(this._supportedLinkDropTypes[i])) {
                // Here we need to to do this manually
                return dt.effectAllowed = dt.dropEffect = "link";
              }
            }
            return dt.effectAllowed = "none";
          ]]>
        </body>
      </method>

      <method name="_onDragOver">
        <parameter name="aEvent"/>
        <body>
          <![CDATA[
            var effects = this._setEffectAllowedForDataTransfer(aEvent);

            var ib = this.mTabDropIndicatorBar;
            if (effects == "" || effects == "none") {
              ib.collapsed = "true";
              return;
            }
            aEvent.preventDefault();
            aEvent.stopPropagation();

            var tabStrip = this.mTabContainer.mTabstrip;
            var ltr = (window.getComputedStyle(this.parentNode, null).direction
                       == "ltr");

            // autoscroll the tab strip if we drag over the scroll
            // buttons, even if we aren't dragging a tab, but then
            // return to avoid drawing the drop indicator
            var pixelsToScroll = 0;
            if (this.mTabContainer.getAttribute("overflow") == "true") {
              var targetAnonid = aEvent.originalTarget.getAttribute("anonid");
              switch (targetAnonid) {
                case "scrollbutton-up":
                  pixelsToScroll = tabStrip.scrollIncrement * -1;
                  break;
                case "scrollbutton-down":
                case "alltabs-button":
                case "newtab-button":
                  pixelsToScroll = tabStrip.scrollIncrement;
                  break;
              }
              if (pixelsToScroll)
                tabStrip.scrollByPixels((ltr ? 1 : -1) * pixelsToScroll);
            }

            if (effects == "link" && aEvent.target.localName == "tab") {
              if (!this.mDragTime) 
                this.mDragTime = Date.now();
              if (Date.now() >= this.mDragTime + this.mDragOverDelay)
                this.mTabContainer.selectedItem = aEvent.target;
              return;
            }

            var newIndex = this.getNewIndex(aEvent);
            var ib = this.mTabDropIndicatorBar;
            var ind = ib.firstChild;
            var scrollRect = tabStrip.scrollClientRect;
            var rect = this.getBoundingClientRect();
            var minMargin = scrollRect.left - rect.left;
            // make sure we don't place the tab drop indicator past the
            // edge, or the containing box will flex and stretch
            // the tab drop indicator bar, which will flex the url bar.  
            // XXX todo
            // just use first value if you can figure out how to get
            // the tab drop indicator to crop instead of flex and stretch
            // the tab drop indicator bar.
            var maxMargin = Math.min(minMargin + scrollRect.width, 
                                     ib.getBoundingClientRect().right -
                                     ind.clientWidth);
            if (!ltr)
              [minMargin, maxMargin] = [this.clientWidth - maxMargin,
                                        this.clientWidth - minMargin];
            var newMargin;
            if (pixelsToScroll) {
              // if we are scrolling, put the drop indicator at the edge
              // so that it doesn't jump while scrolling
              newMargin = (pixelsToScroll > 0) ? maxMargin : minMargin;
            }
            else {
              if (newIndex == this.mTabs.length) {
                let tabRect = this.mTabs[newIndex-1].getBoundingClientRect();
                if (ltr)
                  newMargin = tabRect.right - rect.left;
                else
                  newMargin = rect.right - tabRect.left;
              }
              else {
                let tabRect = this.mTabs[newIndex].getBoundingClientRect();
                if (ltr)
                  newMargin = tabRect.left - rect.left;
                else
                  newMargin = rect.right - tabRect.right;
              }
              // ensure we never place the drop indicator beyond our limits
              if (newMargin < minMargin)
                newMargin = minMargin;
              else if (newMargin > maxMargin)
                newMargin = maxMargin;
            }

            ind.style.MozMarginStart = newMargin + 'px';

            ib.collapsed = false;
          ]]>
        </body>
      </method>

      <method name="_onDrop">
        <parameter name="aEvent"/>
        <body>
          <![CDATA[
            var dt = aEvent.dataTransfer;
            var dropEffect = dt.dropEffect;
            var draggedTab;
            if (dropEffect != "link") { // copy or move
              draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
              // not our drop then
              if (!draggedTab)
                return;
            }

            this.mTabDropIndicatorBar.collapsed = true;
            aEvent.stopPropagation();

            if (draggedTab && (dropEffect == "copy" ||
                draggedTab.parentNode == this.mTabContainer)) {
              var newIndex = this.getNewIndex(aEvent);
              if (dropEffect == "copy") {
                // copy the dropped tab (wherever it's from)
                var newTab = this.duplicateTab(draggedTab);
                this.moveTabTo(newTab, newIndex);
                if (draggedTab.parentNode != this.mTabContainer || aEvent.shiftKey)
                  this.selectedTab = newTab;
              }
              else {
                // move the dropped tab
                if (newIndex > draggedTab._tPos)
                  newIndex--;
                if (newIndex != draggedTab._tPos)
                  this.moveTabTo(draggedTab, newIndex);
              }
            }
            else if (draggedTab) {
              // swap the dropped tab with a new one we create and then close
              // it in the other window (making it seem to have moved between
              // windows)
              newIndex = this.getNewIndex(aEvent);
              newTab = this.addTab("about:blank");
              var newBrowser = this.getBrowserForTab(newTab);
              // Stop the about:blank load
              newBrowser.stop();
              // make sure it has a docshell
              newBrowser.docShell;
              
              this.moveTabTo(newTab, newIndex);
              
              this.swapBrowsersAndCloseOther(newTab, draggedTab);

              // We need to set selectedTab after we've done
              // swapBrowsersAndCloseOther, so that the updateCurrentBrowser
              // it triggers will correctly update our URL bar.
              this.selectedTab = newTab;
            }
            else {
              var url;
              for (var i=0; i < this._supportedLinkDropTypes.length; i++) {
                let dataType = this._supportedLinkDropTypes[i];
                // uri-list: for now, support dropping of the first URL
                // only
                var isURLList = dataType == "text/uri-list";
                let urlData = isURLList ?
                              dt.mozGetDataAt("URL", 0) : dt.mozGetDataAt(dataType, 0);
                if (urlData) {
                  url = transferUtils.retrieveURLFromData(urlData, isURLList ? "text/plain" : dataType);
                  break;
                }
              }
              NS_ASSERT(url, "In the drop event, at least one mime-type should match our supported types");

              // valid urls don't contain spaces ' '; if we have a space it isn't a valid url.
              // Also disallow dropping javascript: or data: urls--bail out
              if (!url || !url.length || url.indexOf(" ", 0) != -1 ||
                  /^\s*(javascript|data):/.test(url))
                return;

              // XXXmano: temporary fix until dragDropSecurityCheck make the
              // drag-session an optional paramter
              var dragService = Cc["@mozilla.org/widget/dragservice;1"].
                                getService(Ci.nsIDragService);
              var dragSession = dragService.getCurrentSession();
              nsDragAndDrop.dragDropSecurityCheck(aEvent, dragSession, url);

              var bgLoad = true;
              try {
                bgLoad = this.mPrefs.getBoolPref("browser.tabs.loadInBackground");
              }
              catch (e) { }

              if (aEvent.shiftKey)
                bgLoad = !bgLoad;

              if (document.getBindingParent(aEvent.originalTarget).localName != "tab" || dropEffect == "copy") {
                // We're adding a new tab.
                newIndex = this.getNewIndex(aEvent);
                newTab = this.loadOneTab(getShortcutOrURI(url), {inBackground: bgLoad});
                this.moveTabTo(newTab, newIndex);
              }
              else {
                // Load in an existing tab.
                var tab = aEvent.target;
                try {
                  this.getBrowserForTab(tab).loadURI(getShortcutOrURI(url));
                  if (this.mCurrentTab != tab && !bgLoad)
                    this.selectedTab = tab;
                } catch(ex) {
                  // Just ignore invalid urls
                }
              }
            }
          ]]>
        </body>
      </method>

      <method name="_onDragEnd">
        <parameter name="aEvent"/>
        <body>
          <![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.

            // * mozUserCancelled = the user pressed ESC to cancel the drag
            var dt = aEvent.dataTransfer;
            if (dt.mozUserCancelled || dt.dropEffect != "none")
              return;

            // Disable detach within the browser toolbox
            var eX = aEvent.screenX;
            var wX = window.screenX;
            // check if the drop point is horizontally within the window
            if (eX > wX && eX < (wX + window.outerWidth)) {
              var bo = this.mTabContainer.mTabstrip.boxObject;
              // also avoid detaching if the the tab was dropped too close to
              // the tabbar (half a tab)
              var endScreenY = bo.screenY + 1.5 * bo.height;
              var eY = aEvent.screenY;
              if (eY < endScreenY && eY > window.screenY)
                return;
            }

            var draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
            this.replaceTabWithWindow(draggedTab);
            aEvent.stopPropagation();
          ]]>
        </body>
      </method>

      <!-- Moves a tab to a new browser window, unless it's already the only tab
           in the current window, in which case this will do nothing. -->
      <method name="replaceTabWithWindow">
        <parameter name="aTab"/>
        <body>
          <![CDATA[
            if (this.mTabs.length == 1)
              return null;

            // tell a new window to take the "dropped" tab
            var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
                      getService(Ci.nsIWindowWatcher);
            return ww.openWindow(window,
                                 getBrowserURL(),
                                 null,
                                 "chrome,dialog=no,all",
                                 aTab);
          ]]>
        </body>
      </method>

      <method name="_onDragLeave">
        <parameter name="aEvent"/>
        <body>
          <![CDATA[
            this.mDragTime = 0;

            // This does not work at all (see bug 458613)
            var target = aEvent.relatedTarget;
            while (target && target != this)
              target = target.parentNode;
            if (target)
              return;

            this.mTabDropIndicatorBar.collapsed = true;
            aEvent.stopPropagation();
          ]]>
        </body>
      </method>

      <method name="moveTabTo">
        <parameter name="aTab"/>
        <parameter name="aIndex"/>
        <body>
        <![CDATA[
          this._lastRelatedTab = null;
          this._browsers = null; // invalidate cache
          this.mTabFilters.splice(aIndex, 0, this.mTabFilters.splice(aTab._tPos, 1)[0]);
          this.mTabListeners.splice(aIndex, 0, this.mTabListeners.splice(aTab._tPos, 1)[0]);

          var oldPosition = aTab._tPos;

          aIndex = aIndex < aTab._tPos ? aIndex: aIndex+1;
          this.mCurrentTab._selected = false;
          // use .item() instead of [] because dragging to the end of the strip goes out of
          // bounds: .item() returns null (so it acts like appendChild), but [] throws
          this.mTabContainer.insertBefore(aTab, this.mTabContainer.childNodes.item(aIndex));
          // invalidate cache, because mTabContainer is about to change
          this._browsers = null;

          var i;
          for (i = 0; i < this.mTabContainer.childNodes.length; i++) {
            this.mTabContainer.childNodes[i]._tPos = i;
            this.mTabContainer.childNodes[i]._selected = false;
          }
          this.mCurrentTab._selected = true;
          this.mTabContainer.mTabstrip.ensureElementIsVisible(this.mCurrentTab, false);

          var evt = document.createEvent("UIEvents");
          evt.initUIEvent("TabMove", true, false, window, oldPosition);
          aTab.dispatchEvent(evt);

          return aTab;
        ]]>
        </body>
      </method>

      <method name="getNewIndex">
        <parameter name="aEvent"/>
        <body>
          <![CDATA[
            var i;
            if (window.getComputedStyle(this.parentNode, null).direction == "ltr") {
              for (i = aEvent.target.localName == "tab" ? aEvent.target._tPos : 0; i < this.mTabs.length; i++)
                if (aEvent.screenX < this.mTabs[i].boxObject.screenX + this.mTabs[i].boxObject.width / 2) 
                  return i;
            } else {
              for (i = aEvent.target.localName == "tab" ? aEvent.target._tPos : 0; i < this.mTabs.length; i++)
                if (aEvent.screenX > this.mTabs[i].boxObject.screenX + this.mTabs[i].boxObject.width / 2)
                  return i;
            }
            return this.mTabs.length;
          ]]>
        </body>
      </method>


      <method name="moveTabForward">
        <body>
          <![CDATA[
            var tabPos = this.mCurrentTab._tPos;
            if (tabPos < this.browsers.length - 1) {
              this.moveTabTo(this.mCurrentTab, tabPos + 1);
              this.mCurrentTab.focus();
            }
            else if (this.arrowKeysShouldWrap)
              this.moveTabToStart();
          ]]>
        </body>
      </method>

      <method name="moveTabBackward">
        <body>
          <![CDATA[
            var tabPos = this.mCurrentTab._tPos;
            if (tabPos > 0) {
              this.moveTabTo(this.mCurrentTab, tabPos - 1);
              this.mCurrentTab.focus();
            }
            else if (this.arrowKeysShouldWrap)
              this.moveTabToEnd();
          ]]>
        </body>
      </method>

      <method name="moveTabToStart">
        <body>
          <![CDATA[
            var tabPos = this.mCurrentTab._tPos;
            if (tabPos > 0) {
              this.moveTabTo(this.mCurrentTab, 0);
              this.mCurrentTab.focus();
            }
          ]]>
        </body>
      </method>

      <method name="moveTabToEnd">
        <body>
          <![CDATA[
            var tabPos = this.mCurrentTab._tPos;
            if (tabPos < this.browsers.length - 1) {
              this.moveTabTo(this.mCurrentTab,
                             this.browsers.length - 1);
              this.mCurrentTab.focus();
            }
          ]]>
        </body>
      </method>

      <method name="moveTabOver">
        <parameter name="aEvent"/>
        <body>
          <![CDATA[
            var direction = window.getComputedStyle(this.parentNode, null).direction;
            if ((direction == "ltr" && aEvent.keyCode == KeyEvent.DOM_VK_RIGHT) ||
                (direction == "rtl" && aEvent.keyCode == KeyEvent.DOM_VK_LEFT))
              this.moveTabForward();
            else
              this.moveTabBackward();
          ]]>
        </body>
      </method>

      <method name="duplicateTab">
        <parameter name="aTab"/><!-- can be from a different window as well -->
        <body>
          <![CDATA[
            // try to have SessionStore duplicate the given tab
            try {
              var ss = Components.classes["@mozilla.org/browser/sessionstore;1"]
                                 .getService(Components.interfaces.nsISessionStore);
              return ss.duplicateTab(window, aTab);
            } catch (ex) {
              // fall back to basic URL copying
              return this.loadOneTab(this.getBrowserForTab(aTab).currentURI.spec);
            }
          ]]>
        </body>
      </method>

      <!-- BEGIN FORWARDED BROWSER PROPERTIES.  IF YOU ADD A PROPERTY TO THE BROWSER ELEMENT
           MAKE SURE TO ADD IT HERE AS WELL. -->
      <property name="canGoBack"
                onget="return this.mCurrentBrowser.canGoBack;"
                readonly="true"/>

      <property name="canGoForward"
                onget="return this.mCurrentBrowser.canGoForward;"
                readonly="true"/>

      <method name="goBack">
        <body>
          <![CDATA[
            return this.mCurrentBrowser.goBack();
          ]]>
        </body>
      </method>

      <method name="goForward">
        <body>
          <![CDATA[
            return this.mCurrentBrowser.goForward();
          ]]>
        </body>
      </method>

      <method name="reload">
        <body>
          <![CDATA[
            return this.mCurrentBrowser.reload();
          ]]>
        </body>
      </method>

      <method name="reloadWithFlags">
        <parameter name="aFlags"/>
        <body>
          <![CDATA[
            return this.mCurrentBrowser.reloadWithFlags(aFlags);
          ]]>
        </body>
      </method>

      <method name="stop">
        <body>
          <![CDATA[
            return this.mCurrentBrowser.stop();
          ]]>
        </body>
      </method>

      <!-- throws exception for unknown schemes -->
      <method name="loadURI">
        <parameter name="aURI"/>
        <parameter name="aReferrerURI"/>
        <parameter name="aCharset"/>
        <body>
          <![CDATA[
            return this.mCurrentBrowser.loadURI(aURI, aReferrerURI, aCharset);
          ]]>
        </body>
      </method>

      <!-- throws exception for unknown schemes -->
      <method name="loadURIWithFlags">
        <parameter name="aURI"/>
        <parameter name="aFlags"/>
        <parameter name="aReferrerURI"/>
        <parameter name="aCharset"/>
        <parameter name="aPostData"/>
        <body>
          <![CDATA[
            return this.mCurrentBrowser.loadURIWithFlags(aURI, aFlags, aReferrerURI, aCharset, aPostData);
          ]]>
        </body>
      </method>

      <method name="goHome">
        <body>
          <![CDATA[
            return this.mCurrentBrowser.goHome();
          ]]>
        </body>
      </method>

      <property name="homePage">
        <getter>
          <![CDATA[
            return this.mCurrentBrowser.homePage;
          ]]>
        </getter>
        <setter>
          <![CDATA[
            this.mCurrentBrowser.homePage = val;
            return val;
          ]]>
        </setter>
      </property>

      <method name="gotoIndex">
        <parameter name="aIndex"/>
        <body>
          <![CDATA[
            return this.mCurrentBrowser.gotoIndex(aIndex);
          ]]>
        </body>
      </method>

      <method name="attachFormFill">
        <body><![CDATA[
          for (var i = 0; i < this.mPanelContainer.childNodes.length; ++i) {
            var cb = this.getBrowserAtIndex(i);
            cb.attachFormFill();
          }
        ]]></body>
      </method>

      <method name="detachFormFill">
        <body><![CDATA[
          for (var i = 0; i < this.mPanelContainer.childNodes.length; ++i) {
            var cb = this.getBrowserAtIndex(i);
            cb.detachFormFill();
          }
        ]]></body>
      </method>

      <property name="pageReport"
                onget="return this.mCurrentBrowser.pageReport;"
                readonly="true"/>

      <property name="currentURI"
                onget="return this.mCurrentBrowser.currentURI;"
                readonly="true"/>

      <field name="_fastFind">null</field>
      <property name="fastFind"
                readonly="true">
        <getter>
        <![CDATA[
          if (!this._fastFind) {
            this._fastFind = Components.classes["@mozilla.org/typeaheadfind;1"]
                                       .createInstance(Components.interfaces.nsITypeAheadFind);
            this._fastFind.init(this.docShell);
          }
          return this._fastFind;
        ]]>
        </getter>
      </property>

      <property name="docShell"
                onget="return this.mCurrentBrowser.docShell"
                readonly="true"/>

      <property name="webNavigation"
                onget="return this.mCurrentBrowser.webNavigation"
                readonly="true"/>

      <property name="webBrowserFind"
                readonly="true"
                onget="return this.mCurrentBrowser.webBrowserFind"/>

      <property name="webProgress"
                readonly="true"
                onget="return this.mCurrentBrowser.webProgress"/>

      <property name="contentWindow"
                readonly="true"
                onget="return this.mCurrentBrowser.contentWindow"/>

      <property name="sessionHistory"
                onget="return this.mCurrentBrowser.sessionHistory;"
                readonly="true"/>

      <property name="markupDocumentViewer"
                onget="return this.mCurrentBrowser.markupDocumentViewer;"
                readonly="true"/>

      <property name="contentViewerEdit"
                onget="return this.mCurrentBrowser.contentViewerEdit;"
                readonly="true"/>

      <property name="contentViewerFile"
                onget="return this.mCurrentBrowser.contentViewerFile;"
                readonly="true"/>

      <property name="documentCharsetInfo"
                onget="return this.mCurrentBrowser.documentCharsetInfo;"
                readonly="true"/>

      <property name="contentDocument"
                onget="return this.mCurrentBrowser.contentDocument;"
                readonly="true"/>

      <property name="contentTitle"
                onget="return this.mCurrentBrowser.contentTitle;"
                readonly="true"/>

      <property name="contentPrincipal"
                onget="return this.mCurrentBrowser.contentPrincipal;"
                readonly="true"/>

      <property name="securityUI"
                onget="return this.mCurrentBrowser.securityUI;"
                readonly="true"/>

      <method name="dragDropSecurityCheck">
        <parameter name="aEvent"/>
        <parameter name="aDragSession"/>
        <parameter name="aUri"/>
        <body>
          <![CDATA[
            nsDragAndDrop.dragDropSecurityCheck(aEvent, aDragSession, aUri);
          ]]>
        </body>
      </method>

      <field name="_keyEventHandler" readonly="true">
      <![CDATA[({
        tabbrowser: this,
        handleEvent: function handleEvent(aEvent) {
          if (!aEvent.isTrusted) {
            // Don't let untrusted events mess with tabs.
            return;
          }

          if ('altKey' in aEvent && aEvent.altKey)
            return;
#ifdef XP_MACOSX
          if ('metaKey' in aEvent && aEvent.metaKey) {
            var offset = 1;
            switch (aEvent.charCode) {
              case '}'.charCodeAt(0):
                offset *= -1;
              case '{'.charCodeAt(0):
                if (window.getComputedStyle(this.tabbrowser, null).direction == "ltr")
                  offset *= -1;

                this.tabbrowser.mTabContainer.advanceSelectedTab(offset, true);
                aEvent.stopPropagation();
                aEvent.preventDefault();
                return;
            }
            if ('shiftKey' in aEvent && aEvent.shiftKey)
              return;
#else
          if (('ctrlKey' in aEvent && aEvent.ctrlKey) &&
              !('shiftKey' in aEvent && aEvent.shiftKey) &&
              !('metaKey' in aEvent && aEvent.metaKey)) {
            if (aEvent.keyCode == KeyEvent.DOM_VK_F4 &&
                this.tabbrowser.mTabBox.handleCtrlPageUpDown) {
              this.tabbrowser.removeCurrentTab();

              aEvent.stopPropagation();
              aEvent.preventDefault();
              return;
            }
#endif
            if (aEvent.target.localName == "tabbrowser") {
              switch (aEvent.keyCode) {
                case KeyEvent.DOM_VK_UP:
                  this.tabbrowser.moveTabBackward();
                  break;
                case KeyEvent.DOM_VK_DOWN:
                  this.tabbrowser.moveTabForward();
                  break;
                case KeyEvent.DOM_VK_RIGHT:
                case KeyEvent.DOM_VK_LEFT:
                  this.tabbrowser.moveTabOver(aEvent);
                  break;
                case KeyEvent.DOM_VK_HOME:
                  this.tabbrowser.moveTabToStart();
                  break;
                case KeyEvent.DOM_VK_END:
                  this.tabbrowser.moveTabToEnd();
                  break;
                default:
                  // Stop the keypress event for the above keyboard
                  // shortcuts only.
                  return;
              }
              aEvent.stopPropagation();
              aEvent.preventDefault();
            }
          }
        }
      })]]>
      </field>

      <property name="userTypedClear"
                onget="return this.mCurrentBrowser.userTypedClear;"
                onset="return this.mCurrentBrowser.userTypedClear = val;"/>

      <property name="userTypedValue"
                onget="return this.mCurrentBrowser.userTypedValue;"
                onset="return this.mCurrentBrowser.userTypedValue = val;"/>

      <method name="createTooltip">
        <parameter name="event"/>
        <body>
          <![CDATA[
            event.stopPropagation();
            var tn = document.tooltipNode;
            if (tn.localName != "tab")
              return false; // Not a tab, so cancel the tooltip
            if ("mOverCloseButton" in tn && tn.mOverCloseButton) {
              event.target.setAttribute("label", tn.getAttribute("closetabtext"));
              return true;
            }
            if (tn.hasAttribute("label")) {
              event.target.setAttribute("label", tn.getAttribute("label"));
              return true;
            }
            return false;
          ]]>
        </body>
      </method>

      <constructor>
        <![CDATA[
          this.mCurrentBrowser = this.mPanelContainer.childNodes[0].firstChild;
          this.mCurrentTab = this.mTabContainer.firstChild;
          document.addEventListener("keypress", this._keyEventHandler, false);

          var uniqueId = "panel" + Date.now();
          this.mPanelContainer.childNodes[0].id = uniqueId;
          this.mTabContainer.childNodes[0].linkedPanel = uniqueId;
          this.mTabContainer.childNodes[0]._tPos = 0;
          this.mTabContainer.childNodes[0].linkedBrowser = this.mPanelContainer.childNodes[0].firstChild;

          // set up the shared autoscroll popup
          this._autoScrollPopup = this.mCurrentBrowser._createAutoScrollPopup();
          this._autoScrollPopup.id = "autoscroller";
          this.appendChild(this._autoScrollPopup);
          this.mCurrentBrowser.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
        ]]>
      </constructor>

      <destructor>
        <![CDATA[
          for (var i = 0; i < this.mTabListeners.length; ++i) {
            this.getBrowserAtIndex(i).webProgress.removeProgressListener(this.mTabFilters[i]);
            this.mTabFilters[i].removeProgressListener(this.mTabListeners[i]);
            this.mTabFilters[i] = null;
            this.mTabListeners[i].destroy();
            this.mTabListeners[i] = null;
            this.getBrowserAtIndex(i).removeEventListener("DOMTitleChanged", this.onTitleChanged, true);
          }
          document.removeEventListener("keypress", this._keyEventHandler, false);
        ]]>
      </destructor>
    </implementation>

    <handlers>
      <handler event="DOMWindowClose" phase="capturing">
        <![CDATA[
          if (!event.isTrusted)
            return;

          const browsers = this.mPanelContainer.childNodes;
          if (browsers.length == 1) {
            // There's only one browser left. If a window is being
            // closed and the window is *not* the window in the
            // browser that's still around, prevent the event's default
            // action to prevent closing a window that's being closed
            // already.
            if (this.getBrowserAtIndex(0).contentWindow != event.target)
              event.preventDefault();

            return;
          }

          var i = 0;
          for (; i < browsers.length; ++i) {
            if (this.getBrowserAtIndex(i).contentWindow == event.target) {
              this.removeTab(this.mTabContainer.childNodes[i]);
              event.preventDefault();

              break;
            }
          }
        ]]>
      </handler>
      <handler event="DOMWillOpenModalDialog" phase="capturing">
        <![CDATA[
          if (!event.isTrusted)
            return;

          // We're about to open a modal dialog, make sure the opening
          // tab is brought to the front.

          var targetTop = event.target.top;

          for (var i = 0; i < browsers.length; ++i) {
            if (this.getBrowserAtIndex(i).contentWindow == targetTop) {
              this.selectedTab = this.mTabContainer.childNodes[i];

              break;
            }
          }
        ]]>
      </handler>
    </handlers>
  </binding>

  <binding id="tabbrowser-arrowscrollbox" extends="chrome://global/content/bindings/scrollbox.xml#arrowscrollbox-clicktoscroll">
    <implementation>
      <!-- Override scrollbox.xml method, since our scrollbox's children are
           inherited from the binding parent -->
      <method name="_getScrollableElements">
        <body><![CDATA[
          return document.getBindingParent(this).childNodes;
        ]]></body>
      </method>
    </implementation>

    <handlers>
      <handler event="underflow"><![CDATA[
         if (event.detail == 0)
           return; // Ignore vertical events

         var tabs = document.getBindingParent(this);
         tabs.removeAttribute("overflow");
      ]]></handler>
      <handler event="overflow"><![CDATA[
         if (event.detail == 0)
           return; // Ignore vertical events

         var tabs = document.getBindingParent(this);
         tabs.setAttribute("overflow", "true");
         this.ensureElementIsVisible(tabs.selectedItem, false);
      ]]></handler>
    </handlers>
  </binding>

  <binding id="tabbrowser-tabs"
           extends="chrome://global/content/bindings/tabbox.xml#tabs">
    <content>
      <xul:stack flex="1" class="tabs-stack">
        <xul:vbox>
          <xul:spacer flex="1"/>
          <xul:hbox class="tabs-bottom" align="center"/>
        </xul:vbox>
        <xul:hbox xbl:inherits="overflow" class="tabs-container">
#ifdef XP_MACOSX
          <xul:stack>
            <xul:spacer class="tabs-left"/>
          </xul:stack>
#endif
          <xul:arrowscrollbox anonid="arrowscrollbox" orient="horizontal" flex="1"
                              style="min-width: 1px;"
#ifndef XP_MACOSX
                              clicktoscroll="true"
#endif
                              class="tabbrowser-arrowscrollbox">
# This is a hack to circumvent bug 472020, otherwise the tabs show up on the
# right of the newtab button.
            <children includes="tab"/>
# This is to ensure anything extensions put here will go before the newtab
# button, necessary due to the previous hack.
            <children/>
            <xul:toolbarbutton class="tabs-newtab-button"
                               command="cmd_newNavigatorTab"
                               tooltiptext="&newTabButton.tooltip;"/>
          </xul:arrowscrollbox>
          <xul:toolbarbutton class="tabs-newtab-button" anonid="newtab-button"
                             command="cmd_newNavigatorTab"
                             tooltiptext="&newTabButton.tooltip;"/>
          <xul:toolbarbutton class="tabs-alltabs-button" anonid="alltabs-button"
                             type="menu"
                             tooltiptext="&listAllTabs.label;">
            <xul:menupopup class="tabs-alltabs-popup" anonid="alltabs-popup"
                           position="after_end"/>
          </xul:toolbarbutton>
          <xul:toolbarbutton anonid="tabs-closebutton"
                             class="close-button tabs-closebutton"/>
        </xul:hbox>
      </xul:stack>
    </content>
    <implementation implements="nsIDOMEventListener">
      <constructor>
        <![CDATA[
          var pb2 =
              Components.classes['@mozilla.org/preferences-service;1'].
              getService(Components.interfaces.nsIPrefBranch2);

          try {
            this.mTabMinWidth = pb2.getIntPref("browser.tabs.tabMinWidth");
          } catch (e) {
          }
          try {
            this.mTabMaxWidth = pb2.getIntPref("browser.tabs.tabMaxWidth");
          } catch (e) {
          }
          try {
            this.mTabClipWidth = pb2.getIntPref("browser.tabs.tabClipWidth");
          } catch (e) {
          }
          try {
            this.mCloseButtons = pb2.getIntPref("browser.tabs.closeButtons");
          } catch (e) {
          }

          this.firstChild.minWidth = this.mTabMinWidth;
          this.firstChild.maxWidth = this.mTabMaxWidth;
          this.adjustTabstrip();

          pb2.addObserver("browser.tabs.closeButtons", 
                          this._prefObserver, false);

          window.addEventListener("resize", this, false);
        ]]>
      </constructor>

      <destructor>
        <![CDATA[
          var pb2 =
              Components.classes['@mozilla.org/preferences-service;1'].
              getService(Components.interfaces.nsIPrefBranch2);
          pb2.removeObserver("browser.tabs.closeButtons", this._prefObserver);
        ]]>
      </destructor>

      <field name="mTabstripWidth">0</field>

      <field name="mTabstrip">
        document.getAnonymousElementByAttribute(this, "anonid", "arrowscrollbox");
      </field>

      <field name="mTabstripClosebutton">
        document.getAnonymousElementByAttribute(this, "anonid", "tabs-closebutton");
      </field>

      <field name="_prefObserver">({
        tabbox: this,
  
        observe: function(subject, topic, data)
        {
          if (topic == "nsPref:changed") {
            switch (data) {
            case "browser.tabs.closeButtons":
              subject.QueryInterface(Components.interfaces.nsIPrefBranch);
              this.tabbox.mCloseButtons = subject.getIntPref("browser.tabs.closeButtons");
              this.tabbox.adjustTabstrip();
              break;
            }
          }
        },
  
        QueryInterface: function(aIID)
        {
          if (aIID.equals(Components.interfaces.nsIObserver) ||
              aIID.equals(Components.interfaces.nsISupports))
            return this;
          throw Components.results.NS_NOINTERFACE;
        }
        });
      </field>
      <field name="mTabMinWidth">100</field>
      <field name="mTabMaxWidth">250</field>
      <field name="mTabClipWidth">140</field>
      <field name="mCloseButtons">1</field>

      <method name="adjustTabstrip">
        <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:
            if (this.firstChild.getBoundingClientRect().width > this.mTabClipWidth)
              this.setAttribute("closebuttons", "alltabs");
            else
              this.setAttribute("closebuttons", "activetab");
            break;
          case 2:
          case 3:
            this.setAttribute("closebuttons", "noclose");
            break;
          }
          this.mTabstripClosebutton.collapsed = this.mCloseButtons != 3;
        ]]></body>
      </method>
        
      <field name="_mPrefs">null</field>
      <property name="mPrefs" readonly="true">
        <getter>
        <![CDATA[
          if (!this._mPrefs) {
            this._mPrefs =
              Components.classes['@mozilla.org/preferences-service;1'].
              getService(Components.interfaces.nsIPrefBranch2);
          }
          return this._mPrefs;
        ]]>
        </getter>
      </property>

      <method name="_handleTabSelect">
        <body><![CDATA[
          this.mTabstrip.ensureElementIsVisible(this.selectedItem);
        ]]></body>
      </method>

      <method name="_fillTrailingGap">
        <body><![CDATA[
          try {
            // if we're at the right side (and not the logical end,
            // which is why this works for both LTR and RTL)
            // of the tabstrip, we need to ensure that we stay
            // completely scrolled to the right side
            var tabStrip = this.mTabstrip;
            if (tabStrip.scrollPosition + tabStrip.scrollClientSize >=
                tabStrip.scrollSize)
              tabStrip.scrollByPixels(-1);
          } catch (e) {}
        ]]></body>
      </method>

      <method name="handleEvent">
        <parameter name="aEvent"/>
        <body><![CDATA[
          switch (aEvent.type) {
            case "resize":
              var width = this.mTabstrip.boxObject.width;
              if (width != this.mTabstripWidth) {
                this.adjustTabstrip();
                this._fillTrailingGap();
                this._handleTabSelect();
                this.mTabstripWidth = width;
              }
              break;
          }
        ]]></body>
      </method>

      <field name="mAllTabsPopup">
        document.getAnonymousElementByAttribute(this, 
                                                "anonid", "alltabs-popup");
      </field>

      <field name="_animateElement">
        this.mTabstrip._scrollButtonDown;
      </field>
      
      <method name="_notifyBackgroundTab">
        <parameter name="aTab"/>
        <body><![CDATA[
          var scrollRect = this.mTabstrip.scrollClientRect;
          var tab = aTab.getBoundingClientRect();

          // Is the new tab already completely visible?
          if (scrollRect.left <= tab.left && tab.right <= scrollRect.right)
            return;

          if (this.mTabstrip.smoothScroll) {
            let selected = this.selectedItem.getBoundingClientRect();

            // Can we make both the new tab and the selected tab completely visible?
            if (Math.max(tab.right - selected.left, selected.right - tab.left) <=
                scrollRect.width) {
              this.mTabstrip.ensureElementIsVisible(aTab);
              return;
            }

            this.mTabstrip._smoothScrollByPixels(this.mTabstrip._isRTLScrollbox ?
                                                 selected.right - scrollRect.right :
                                                 selected.left - scrollRect.left);
          }

          if (!this._animateElement.hasAttribute("notifybgtab")) {
            this._animateElement.setAttribute("notifybgtab", "true");
            setTimeout(function (ele) {
              ele.removeAttribute("notifybgtab");
            }, 150, this._animateElement);
          }
        ]]></body>
      </method>
    </implementation>
    <handlers>
      <handler event="TabSelect" action="this._handleTabSelect();"/>
    </handlers>
  </binding>

  <!-- close-tab-button binding
       This binding relies on the structure of the tabbrowser binding.
       Therefore it should only be used as a child of the tab or the tabs
       element (in both cases, when they are anonymous nodes of <tabbrowser>).
       This binding is exposed as a pseudo-public-API so themes can customize
       the tabbar appearance without having to be scriptable
       (see globalBindings.xml in Pinstripe for example).
  -->
  <binding id="tabbrowser-close-tab-button"
           extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-image">
    <handlers>
      <handler event="click" button="0"><![CDATA[
        var bindingParent = document.getBindingParent(this);
        if (bindingParent) {
          var tabbedBrowser = document.getBindingParent(bindingParent);
          if (bindingParent.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.
             *
             * We don't want to ignore processing of more than one click event,
             * though, since the user might actually be repeatedly clicking to
             * close many tabs at once.
             */
            if (event.detail > 1 && !this._ignoredClick) {
              this._ignoredClick = true;
              return;
            }

            // Reset the "ignored click" flag
            this._ignoredClick = false;

            tabbedBrowser.removeTab(bindingParent);
            tabbedBrowser._blockDblClick = true;

            /* 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).
             */
            var clickedOnce = false;
            function enableDblClick(event) {
              if (event.detail == 1 && !clickedOnce) {
                clickedOnce = true;
                return;
              }
              setTimeout(function() {
                tabbedBrowser._blockDblClick = false;
              }, 0);
              tabbedBrowser.removeEventListener("click", enableDblClick, false);
            }
            tabbedBrowser.addEventListener("click", enableDblClick, false);
          }
          else // "tabs"
            tabbedBrowser.removeCurrentTab();
        }
      ]]></handler>
      <handler event="dblclick" button="0" phase="capturing">
        // for the one-close-button case
        event.stopPropagation();
      </handler>
    </handlers>
  </binding>

  <binding id="tabbrowser-tab" display="xul:hbox"
           extends="chrome://global/content/bindings/tabbox.xml#tab">
    <content closetabtext="&closeTab.label;">
      <xul:image xbl:inherits="validate,src=image"
                 class="tab-icon-image"
                 role="presentation"/>
      <xul:label flex="1"
                 xbl:inherits="value=label,crop,accesskey"
                 class="tab-text"
                 role="presentation"/>
      <xul:toolbarbutton anonid="close-button"
                         tabindex="-1"
                         class="tab-close-button"/>
    </content>

    <implementation>
      <field name="mOverCloseButton">false</field>
      <field name="mCorrespondingMenuitem">null</field>
    </implementation>

    <handlers>
      <handler event="mouseover">
        var anonid = event.originalTarget.getAttribute("anonid");
        if (anonid == "close-button")
          this.mOverCloseButton = true;
      </handler>
      <handler event="mouseout">
        var anonid = event.originalTarget.getAttribute("anonid");
        if (anonid == "close-button")
          this.mOverCloseButton = false;
      </handler>
      <handler event="dragstart" phase="capturing">
        this.style.MozUserFocus = '';
      </handler>
      <handler event="mousedown" button="0" phase="capturing">
      <![CDATA[
        if (this.mOverCloseButton) {
          event.stopPropagation();
        }
        else {
          this.style.MozUserFocus = 'ignore';
          this.clientTop; // just using this to flush style updates
        }
      ]]>
      </handler>
      <handler event="mousedown" button="1">
        this.style.MozUserFocus = 'ignore';
        this.clientTop;
      </handler>
      <handler event="mousedown" button="2">
        this.style.MozUserFocus = 'ignore';
        this.clientTop;
      </handler>
      <handler event="mouseup">
        this.style.MozUserFocus = '';
      </handler>
    </handlers>
  </binding>

  <binding id="tabbrowser-alltabs-popup"
           extends="chrome://global/content/bindings/popup.xml#popup">
    <implementation implements="nsIDOMEventListener">
      <method name="_menuItemOnCommand">
        <parameter name="aEvent"/>
        <body><![CDATA[
          var tabcontainer = document.getBindingParent(this);
          tabcontainer.selectedItem = aEvent.target.tab;
        ]]></body>
      </method>

      <method name="_tabOnAttrModified">
        <parameter name="aEvent"/>
        <body><![CDATA[
          var tab = aEvent.target;
          this._setMenuitemAttributes(tab.mCorrespondingMenuitem, tab);
        ]]></body>
      </method>

      <method name="_tabOnTabClose">
        <parameter name="aEvent"/>
        <body><![CDATA[
          var menuItem = aEvent.target.mCorrespondingMenuitem;
          if (menuItem)
            this.removeChild(menuItem);
        ]]></body>
      </method>

      <method name="handleEvent">
        <parameter name="aEvent"/>
        <body><![CDATA[
          if (!aEvent.isTrusted)
            return;

          switch (aEvent.type) {
            case "command":
              this._menuItemOnCommand(aEvent);
              break;
            case "TabAttrModified":
              this._tabOnAttrModified(aEvent);
              break;
            case "TabClose":
              this._tabOnTabClose(aEvent);
              break;
            case "TabOpen":
              this._createTabMenuItem(aEvent.originalTarget);
              break;
            case "scroll":
              this._updateTabsVisibilityStatus();
              break;
          }
        ]]></body>
      </method>

      <method name="_updateTabsVisibilityStatus">
        <body><![CDATA[
          var tabContainer = document.getBindingParent(this);
          // We don't want menu item decoration unless there is overflow.
          if (tabContainer.getAttribute("overflow") != "true")
            return;

          var tabstripBO = tabContainer.mTabstrip.scrollBoxObject;
          for (var i = 0; i < this.childNodes.length; i++) {
            var curTabBO = this.childNodes[i].tab.boxObject;
            if (curTabBO.screenX >= tabstripBO.screenX &&
                curTabBO.screenX + curTabBO.width <= tabstripBO.screenX + tabstripBO.width)
              this.childNodes[i].setAttribute("tabIsVisible", "true"); 
            else
              this.childNodes[i].removeAttribute("tabIsVisible");
          }
        ]]></body>
      </method>

      <method name="_createTabMenuItem">
        <parameter name="aTab"/>
        <body><![CDATA[
          var menuItem = document.createElementNS(
            "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", 
            "menuitem");

          menuItem.setAttribute("class", "menuitem-iconic alltabs-item");

          this._setMenuitemAttributes(menuItem, aTab);

          // Keep some attributes of the menuitem in sync with its
          // corresponding tab (e.g. the tab label)
          aTab.mCorrespondingMenuitem = menuItem;
          menuItem.tab = aTab;
          menuItem.addEventListener("command", this, false);

          this.appendChild(menuItem);
          return menuItem;
        ]]></body>
      </method>

      <method name="_setMenuitemAttributes">
        <parameter name="aMenuitem"/>
        <parameter name="aTab"/>
        <body><![CDATA[
          aMenuitem.setAttribute("label", aTab.label);
          aMenuitem.setAttribute("crop", aTab.getAttribute("crop"));
          aMenuitem.setAttribute("image", aTab.getAttribute("image"));

          if (aTab.hasAttribute("busy"))
            aMenuitem.setAttribute("busy", aTab.getAttribute("busy"));
          else
            aMenuitem.removeAttribute("busy");

          if (aTab.selected)
            aMenuitem.setAttribute("selected", "true");
          else
            aMenuitem.removeAttribute("selected");
        ]]></body>
      </method>
    </implementation>

    <handlers>
      <handler event="popupshowing">
      <![CDATA[
        // set up the menu popup
        var tabcontainer = document.getBindingParent(this);
        var tabs = tabcontainer.childNodes;

        // Listen for changes in the tab bar.
        tabcontainer.addEventListener("TabOpen", this, false);
        tabcontainer.addEventListener("TabAttrModified", this, false);
        tabcontainer.addEventListener("TabClose", this, false);
        tabcontainer.mTabstrip.addEventListener("scroll", this, false);

        for (var i = 0; i < tabs.length; i++) {
          this._createTabMenuItem(tabs[i]);
        }
        this._updateTabsVisibilityStatus();
      ]]></handler>

      <handler event="popuphidden">
      <![CDATA[
        // clear out the menu popup and remove the listeners
        while (this.hasChildNodes()) {
          var menuItem = this.lastChild;
          menuItem.removeEventListener("command", this, false);
          menuItem.tab.mCorrespondingMenuitem = null;
          this.removeChild(menuItem);
        }
        var tabcontainer = document.getBindingParent(this);
        tabcontainer.mTabstrip.removeEventListener("scroll", this, false);
        tabcontainer.removeEventListener("TabOpen", this, false);
        tabcontainer.removeEventListener("TabAttrModified", this, false);
        tabcontainer.removeEventListener("TabClose", this, false);
      ]]></handler>

      <handler event="DOMMenuItemActive">
      <![CDATA[
        var tab = event.target.tab;
        if (tab) {
          var statusText = tab.linkedBrowser.currentURI.spec;
          if (statusText == "about:blank") {
            // XXXhack: Passing a space here (and not "")
            // to make sure the the browser implementation would
            // still consider it a hovered link.
            statusText = " ";
          }

          XULBrowserWindow.setOverLink(statusText, null);
        }
      ]]></handler>

      <handler event="DOMMenuItemInactive">
      <![CDATA[
        XULBrowserWindow.setOverLink("", null);
      ]]></handler>

    </handlers>
  </binding>

</bindings>