<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!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" class="tabbrowser-tabbox"
                  flex="1" eventnode="document" xbl:inherits="handleCtrlPageUpDown"
                  onselect="if (event.target.localName == 'tabpanels') this.parentNode.updateCurrentBrowser();">
        <xul:tabpanels flex="1" class="plain" selectedIndex="0" anonid="panelcontainer">
          <xul:notificationbox flex="1">
            <xul:hbox flex="1" class="browserSidebarContainer">
              <xul:vbox flex="1" class="browserContainer">
                <xul:stack flex="1" class="browserStack" anonid="browserStack">
                  <xul:browser anonid="initialBrowser" type="content-primary" message="true" disablehistory="true"
                               xbl:inherits="tooltip=contenttooltip,contextmenu=contentcontextmenu,autocompletepopup"/>
                </xul:stack>
              </xul:vbox>
            </xul:hbox>
          </xul:notificationbox>
        </xul:tabpanels>
      </xul:tabbox>
      <children/>
    </content>
    <implementation implements="nsIDOMEventListener, nsIMessageListener">

      <property name="tabContextMenu" readonly="true"
                onget="return this.tabContainer.contextMenu;"/>

      <field name="tabContainer" readonly="true">
        document.getElementById(this.getAttribute("tabcontainer"));
      </field>
      <field name="tabs" readonly="true">
        this.tabContainer.childNodes;
      </field>

      <property name="visibleTabs" readonly="true">
        <getter><![CDATA[
          if (!this._visibleTabs)
            this._visibleTabs = Array.filter(this.tabs,
                                             function (tab) !tab.hidden && !tab.closing);
          return this._visibleTabs;
        ]]></getter>
      </property>

      <field name="closingTabsEnum" readonly="true">({ ALL: 0, OTHER: 1, TO_END: 2 });</field>

      <field name="_visibleTabs">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="_placesAutocomplete" readonly="true">
         Components.classes["@mozilla.org/autocomplete/search;1?name=history"]
                   .getService(Components.interfaces.mozIPlacesAutoComplete);
      </field>
      <field name="mTabBox" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "tabbox");
      </field>
      <field name="mPanelContainer" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "panelcontainer");
      </field>
      <field name="mStringBundle">
        document.getAnonymousElementByAttribute(this, "anonid", "tbstringbundle");
      </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">
        []
      </field>
      <field name="mTabFilters">
        []
      </field>
      <field name="mIsBusy">
        false
      </field>
      <field name="arrowKeysShouldWrap" readonly="true">
        false
      </field>

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

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

      <property name="_numPinnedTabs" readonly="true">
        <getter><![CDATA[
          for (var i = 0; i < this.tabs.length; i++) {
            if (!this.tabs[i].pinned)
              break;
          }
          return i;
        ]]></getter>
      </property>

      <method name="updateWindowResizers">
        <body><![CDATA[
          if (!window.gShowPageResizers)
            return;

          var show = document.getElementById("addon-bar").collapsed &&
                     window.windowState == window.STATE_NORMAL;
          for (let i = 0; i < this.browsers.length; i++) {
            this.browsers[i].showWindowResizer = show;
          }
        ]]></body>
      </method>

      <method name="_setCloseKeyState">
        <parameter name="aEnabled"/>
        <body><![CDATA[
          let keyClose = document.getElementById("key_close");
          let closeKeyEnabled = keyClose.getAttribute("disabled") != "true";
          if (closeKeyEnabled == aEnabled)
            return;

          if (aEnabled)
            keyClose.removeAttribute("disabled");
          else
            keyClose.setAttribute("disabled", "true");

          // We also want to remove the keyboard shortcut from the file menu
          // when the shortcut is disabled, and bring it back when it's
          // renabled.
          //
          // Fixing bug 630826 could make that happen automatically.
          // Fixing bug 630830 could avoid the ugly hack below.

          let closeMenuItem = document.getElementById("menu_close");
          let parentPopup = closeMenuItem.parentNode;
          let nextItem = closeMenuItem.nextSibling;
          let clonedItem = closeMenuItem.cloneNode(true);

          parentPopup.removeChild(closeMenuItem);

          if (aEnabled)
            clonedItem.setAttribute("key", "key_close");
          else
            clonedItem.removeAttribute("key");

          parentPopup.insertBefore(clonedItem, nextItem);
        ]]></body>
      </method>

      <method name="pinTab">
        <parameter name="aTab"/>
        <body><![CDATA[
          if (aTab.pinned)
            return;

          if (aTab.hidden)
            this.showTab(aTab);

          this.moveTabTo(aTab, this._numPinnedTabs);
          aTab.setAttribute("pinned", "true");
          this.tabContainer._unlockTabSizing();
          this.tabContainer._positionPinnedTabs();
          this.tabContainer.adjustTabstrip();

          this.getBrowserForTab(aTab).docShell.isAppTab = true;

          if (aTab.selected)
            this._setCloseKeyState(false);

          let event = document.createEvent("Events");
          event.initEvent("TabPinned", true, false);
          aTab.dispatchEvent(event);
        ]]></body>
      </method>

      <method name="unpinTab">
        <parameter name="aTab"/>
        <body><![CDATA[
          if (!aTab.pinned)
            return;

          this.moveTabTo(aTab, this._numPinnedTabs - 1);
          aTab.setAttribute("fadein", "true");
          aTab.removeAttribute("pinned");
          aTab.style.MozMarginStart = "";
          this.tabContainer._unlockTabSizing();
          this.tabContainer._positionPinnedTabs();
          this.tabContainer.adjustTabstrip();

          this.getBrowserForTab(aTab).docShell.isAppTab = false;

          if (aTab.selected)
            this._setCloseKeyState(true);

          let event = document.createEvent("Events");
          event.initEvent("TabUnpinned", true, false);
          aTab.dispatchEvent(event);
        ]]></body>
      </method>

      <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 tab = this._getTabForContentWindow(aDocument.defaultView);
            return tab ? tab._tPos : -1;
          ]]>
        </body>
      </method>

      <method name="getBrowserForDocument">
        <parameter name="aDocument"/>
        <body>
          <![CDATA[
            var tab = this._getTabForContentWindow(aDocument.defaultView);
            return tab ? tab.linkedBrowser : null;
          ]]>
        </body>
      </method>

      <method name="_getTabForContentWindow">
        <parameter name="aWindow"/>
        <body>
        <![CDATA[
          for (let i = 0; i < this.browsers.length; i++) {
            if (this.browsers[i].contentWindow == aWindow)
              return this.tabs[i];
          }
          return null;
        ]]>
        </body>
      </method>

      <method name="_getTabForBrowser">
        <parameter name="aBrowser"/>
        <body>
        <![CDATA[
          for (let i = 0; i < this.tabs.length; i++) {
            if (this.tabs[i].linkedBrowser == aBrowser)
              return this.tabs[i];
          }
          return null;
        ]]>
        </body>
      </method>

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

      <method name="getSidebarContainer">
        <parameter name="aBrowser"/>
        <body>
          <![CDATA[
            return this.getBrowserContainer(aBrowser).parentNode;
          ]]>
        </body>
      </method>

      <method name="getBrowserContainer">
        <parameter name="aBrowser"/>
        <body>
          <![CDATA[
            return (aBrowser || this.mCurrentBrowser).parentNode.parentNode;
          ]]>
        </body>
      </method>

      <method name="getTabModalPromptBox">
        <parameter name="aBrowser"/>
        <body>
          <![CDATA[
            const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
            let browser = (aBrowser || this.mCurrentBrowser);
            let stack = browser.parentNode;
            let self = this;

            let promptBox = {
              appendPrompt : function(args, onCloseCallback) {
                let newPrompt = document.createElementNS(XUL_NS, "tabmodalprompt");
                stack.appendChild(newPrompt);
                browser.setAttribute("tabmodalPromptShowing", true);

                newPrompt.clientTop; // style flush to assure binding is attached

                let tab = self._getTabForContentWindow(browser.contentWindow);
                newPrompt.init(args, tab, onCloseCallback);
                return newPrompt;
              },

              removePrompt : function(aPrompt) {
                stack.removeChild(aPrompt);

                let prompts = this.listPrompts();
                if (prompts.length) {
                  let prompt = prompts[prompts.length - 1];
                  prompt.Dialog.setDefaultFocus();
                } else {
                  browser.removeAttribute("tabmodalPromptShowing");
                  browser.focus();
                }
              },

              listPrompts : function(aPrompt) {
                let els = stack.getElementsByTagNameNS(XUL_NS, "tabmodalprompt");
                // NodeList --> real JS array
                let prompts = Array.slice(els);
                return prompts;
              },
            };

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

      <method name="_callProgressListeners">
        <parameter name="aBrowser"/>
        <parameter name="aMethod"/>
        <parameter name="aArguments"/>
        <parameter name="aCallGlobalListeners"/>
        <parameter name="aCallTabsListeners"/>
        <body><![CDATA[
          var rv = true;

          if (!aBrowser)
            aBrowser = this.mCurrentBrowser;

          if (aCallGlobalListeners != false &&
              aBrowser == this.mCurrentBrowser) {
            this.mProgressListeners.forEach(function (p) {
              if (aMethod in p) {
                try {
                  if (!p[aMethod].apply(p, aArguments))
                    rv = false;
                } catch (e) {
                  // don't inhibit other listeners
                  Components.utils.reportError(e);
                }
              }
            });
          }

          if (aCallTabsListeners != false) {
            aArguments.unshift(aBrowser);

            this.mTabsProgressListeners.forEach(function (p) {
              if (aMethod in p) {
                try {
                  if (!p[aMethod].apply(p, aArguments))
                    rv = false;
                } catch (e) {
                  // don't inhibit other listeners
                  Components.utils.reportError(e);
                }
              }
            });
          }

          return rv;
        ]]></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 UI 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 () {
              delete this.mTab;
              delete this.mBrowser;
              delete this.mTabBrowser;
            },

            _callProgressListeners: function () {
              Array.unshift(arguments, this.mBrowser);
              return this.mTabBrowser._callProgressListeners.apply(this.mTabBrowser, arguments);
            },

            _shouldShowProgress: function (aRequest) {
              if (this.mBlank)
                return false;

              if (gMultiProcessBrowser)
                return true;

              // Don't show progress indicators in tabs for about: URIs
              // pointing to local resources.
              try {
                let channel = aRequest.QueryInterface(Ci.nsIChannel);
                if (channel.originalURI.schemeIs("about") &&
                    (channel.URI.schemeIs("jar") || channel.URI.schemeIs("file")))
                  return false;
              } catch (e) {}

              return true;
            },

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

              if (!this._shouldShowProgress(aRequest))
                return;

              if (this.mTotalProgress)
                this.mTab.setAttribute("progress", "true");

              this._callProgressListeners("onProgressChange",
                                          [aWebProgress, aRequest,
                                           aCurSelfProgress, aMaxSelfProgress,
                                           aCurTotalProgress, aMaxTotalProgress]);
            },

            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.isTopLevel)
                  this.mBrowser.userTypedClear += 2;

                if (this._shouldShowProgress(aRequest)) {
                  if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) {
                    this.mTab.setAttribute("busy", "true");
                    if (!gMultiProcessBrowser) {
                      if (!(this.mBrowser.docShell.loadType & Ci.nsIDocShell.LOAD_CMD_RELOAD))
                        this.mTabBrowser.setTabTitleLoading(this.mTab);
                    }
                  }

                  if (this.mTab.selected)
                    this.mTabBrowser.mIsBusy = true;
                }
              }
              else if (aStateFlags & nsIWebProgressListener.STATE_STOP &&
                       aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {

                if (this.mTab.hasAttribute("busy")) {
                  this.mTab.removeAttribute("busy");
                  this.mTabBrowser._tabAttrModified(this.mTab);
                  if (!this.mTab.selected)
                    this.mTab.setAttribute("unread", "true");
                }
                this.mTab.removeAttribute("progress");

                if (aWebProgress.isTopLevel) {
                  if (!Components.isSuccessCode(aStatus) &&
                      !isTabEmpty(this.mTab)) {
                    // Restore the current document's location in case the
                    // request was stopped (possibly from a content script)
                    // before the location changed.

                    this.mBrowser.userTypedValue = null;

                    if (this.mTab.selected && gURLBar)
                      URLBarSetURI();
                  } else {
                    // 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;

                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.connecting"))
                  this.mTabBrowser.setTabTitle(this.mTab);

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

              if (oldBlank) {
                this._callProgressListeners("onUpdateCurrentBrowser",
                                            [aStateFlags, aStatus, "", 0],
                                            true, false);
              } else {
                this._callProgressListeners("onStateChange",
                                            [aWebProgress, aRequest, aStateFlags, aStatus],
                                            true, false);
              }

              this._callProgressListeners("onStateChange",
                                          [aWebProgress, aRequest, aStateFlags, aStatus],
                                          false);

              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,
                                        aFlags) {
              // OnLocationChange is called for both the top-level content
              // and the subframes.
              let topLevel = aWebProgress.isTopLevel;

              if (topLevel) {
                // The document loaded correctly, clear the value if we should
                if (this.mBrowser.userTypedClear > 0 ||
                    (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE))
                  this.mBrowser.userTypedValue = null;

                // Clear out the missing plugins list since it's related to the
                // previous location.
                this.mBrowser.missingPlugins = null;

                // Don't clear the favicon if this onLocationChange was
                // triggered by a pushState or a replaceState.  See bug 550565.
                if (!gMultiProcessBrowser) {
                  if (aWebProgress.isLoadingDocument &&
                      !(this.mBrowser.docShell.loadType & Ci.nsIDocShell.LOAD_CMD_PUSHSTATE))
                    this.mBrowser.mIconURL = null;
                }

                let autocomplete = this.mTabBrowser._placesAutocomplete;
                if (this.mBrowser.registeredOpenURI) {
                  autocomplete.unregisterOpenPage(this.mBrowser.registeredOpenURI);
                  delete this.mBrowser.registeredOpenURI;
                }
                // Tabs in private windows aren't registered as "Open" so
                // that they don't appear as switch-to-tab candidates.
                if (!isBlankPageURL(aLocation.spec) &&
                    (!PrivateBrowsingUtils.isWindowPrivate(window) ||
                    PrivateBrowsingUtils.permanentPrivateBrowsing)) {
                  autocomplete.registerOpenPage(aLocation);
                  this.mBrowser.registeredOpenURI = aLocation;
                }
              }

              if (!this.mBlank) {
                this._callProgressListeners("onLocationChange",
                                            [aWebProgress, aRequest, aLocation,
                                             aFlags]);
              }

              if (topLevel)
                this.mBrowser.lastURI = aLocation;
            },

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

              this._callProgressListeners("onStatusChange",
                                          [aWebProgress, aRequest, aStatus, aMessage]);

              this.mMessage = aMessage;
            },

            onSecurityChange: function (aWebProgress, aRequest, aState) {
              this._callProgressListeners("onSecurityChange",
                                          [aWebProgress, aRequest, aState]);
            },

            onRefreshAttempted: function (aWebProgress, aURI, aDelay, aSameURI) {
              return this._callProgressListeners("onRefreshAttempted",
                                                 [aWebProgress, aURI, aDelay, aSameURI]);
            },

            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;
            }
          });
        ]]>
        </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.setAndFetchFaviconForPage(browser.currentURI,
                                                             aURI, false,
                                                             PrivateBrowsingUtils.isWindowPrivate(window) ?
                                                               this.mFaviconService.FAVICON_LOAD_PRIVATE :
                                                               this.mFaviconService.FAVICON_LOAD_NON_PRIVATE);
            }

            let sizedIconUrl = browser.mIconURL || "";
            if (sizedIconUrl) {
              let size = Math.round(16 * window.devicePixelRatio);
              sizedIconUrl += (sizedIconUrl.contains("#") ? "&" : "#") +
                              "-moz-resolution=" + size + "," + size;
            }
            if (sizedIconUrl != aTab.getAttribute("image")) {
              if (sizedIconUrl)
                aTab.setAttribute("image", sizedIconUrl);
              else
                aTab.removeAttribute("image");
              this._tabAttrModified(aTab);
            }

            this._callProgressListeners(browser, "onLinkIconAvailable", [browser.mIconURL]);
          ]]>
        </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="shouldLoadFavIcon">
        <parameter name="aURI"/>
        <body>
          <![CDATA[
            return (aURI &&
                    Services.prefs.getBoolPref("browser.chrome.site_icons") &&
                    Services.prefs.getBoolPref("browser.chrome.favicons") &&
                    ("schemeIs" in aURI) && (aURI.schemeIs("http") || aURI.schemeIs("https")));
          ]]>
        </body>
      </method>

      <method name="useDefaultIcon">
        <parameter name="aTab"/>
        <body>
          <![CDATA[
            // Bug 691610 - e10s support for useDefaultIcon
            if (gMultiProcessBrowser)
              return;

            var browser = this.getBrowserForTab(aTab);
            var docURIObject = browser.contentDocument.documentURIObject;
            var icon = null;
            if (browser.contentDocument instanceof ImageDocument) {
              if (Services.prefs.getBoolPref("browser.chrome.site_icons")) {
                let sz = Services.prefs.getIntPref("browser.chrome.image_icons.max_size");
                try {
                  let req = browser.contentDocument.imageRequest;
                  if (req &&
                      req.image &&
                      req.image.width <= sz &&
                      req.image.height <= sz)
                    icon = 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)) {
              let url = docURIObject.prePath + "/favicon.ico";
              if (!this.isFailedIcon(url))
                icon = url;
            }
            this.setIcon(aTab, icon);
          ]]>
        </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="getWindowTitleForBrowser">
        <parameter name="aBrowser"/>
        <body>
          <![CDATA[
            var newTitle = "";
            var docElement = this.ownerDocument.documentElement;
            var sep = docElement.getAttribute("titlemenuseparator");

            // Strip out any null bytes in the content title, since the
            // underlying widget implementations of nsWindow::SetTitle pass
            // null-terminated strings to system APIs.
            var docTitle = aBrowser.contentTitle.replace("\0", "", "g");

            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").contains("location")) {
                var uri = this.mURIFixup.createExposableURI(
                            aBrowser.currentURI);
                if (uri.scheme == "about")
                  newTitle = uri.spec + sep + newTitle;
                else
                  newTitle = uri.prePath + sep + newTitle;
              }
            } catch (e) {}

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

      <method name="updateTitlebar">
        <body>
          <![CDATA[
            if ("TabView" in window && TabView.isVisible()) {
              // ToDo: this will be removed when we gain ability to draw to the menu bar.
              // Bug 586175
              this.ownerDocument.title = TabView.windowTitle;
            }
            else {
              this.ownerDocument.title = this.getWindowTitleForBrowser(this.mCurrentBrowser);
            }
          ]]>
        </body>
      </method>

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

            if (!aForceUpdate) {
              TelemetryStopwatch.start("FX_TAB_SWITCH_UPDATE_MS");
              window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils)
                                                             .beginTabSwitch();
            }

            var oldTab = this.mCurrentTab;

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

            if (this._lastRelatedTab) {
              if (!this._lastRelatedTab.selected)
                this._lastRelatedTab.owner = null;
              this._lastRelatedTab = null;
            }

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

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

            newBrowser.setAttribute("type", "content-primary");
            newBrowser.docShellIsActive =
              (window.windowState != window.STATE_MINIMIZED);
            this.mCurrentBrowser = newBrowser;
            this.mCurrentTab = this.tabContainer.selectedItem;
            this.showTab(this.mCurrentTab);

            var backForwardContainer = document.getElementById("unified-back-forward-button");
            if (backForwardContainer) {
              backForwardContainer.setAttribute("switchingtabs", "true");
              window.addEventListener("MozAfterPaint", function removeSwitchingtabsAttr() {
                window.removeEventListener("MozAfterPaint", removeSwitchingtabsAttr);
                backForwardContainer.removeAttribute("switchingtabs");
              });
            }

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

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

            // Bug 666809 - SecurityUI support for e10s
            var webProgress = this.mCurrentBrowser.webProgress;
            var securityUI = this.mCurrentBrowser.securityUI;

            this._callProgressListeners(null, "onLocationChange",
                                        [webProgress, null, loc, 0], true,
                                        false);

            if (securityUI) {
              this._callProgressListeners(null, "onSecurityChange",
                                          [webProgress, null, securityUI.state], true, false);
            }

            var listener = this.mTabListeners[this.tabContainer.selectedIndex] || null;
            if (listener && listener.mStateFlags) {
              this._callProgressListeners(null, "onUpdateCurrentBrowser",
                                          [listener.mStateFlags, listener.mStatus,
                                           listener.mMessage, listener.mTotalProgress],
                                          true, false);
            }

            if (!this._previewMode) {
              this.mCurrentTab.removeAttribute("unread");
              this.selectedTab.lastAccessed = Date.now();

              // Bug 666816 - TypeAheadFind support for e10s
              if (!gMultiProcessBrowser)
                this._fastFind.setDocShell(this.mCurrentBrowser.docShell);

              this.updateTitlebar();

              this.mCurrentTab.removeAttribute("titlechanged");
            }

            // 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;
              this._callProgressListeners(null, "onStateChange",
                                          [webProgress, null,
                                           nsIWebProgressListener.STATE_START |
                                           nsIWebProgressListener.STATE_IS_NETWORK, 0],
                                          true, false);
            }

            // 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;
              this._callProgressListeners(null, "onStateChange",
                                          [webProgress, null,
                                           nsIWebProgressListener.STATE_STOP |
                                           nsIWebProgressListener.STATE_IS_NETWORK, 0],
                                          true, false);
            }

            this._setCloseKeyState(!this.mCurrentTab.pinned);

            // 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.
              let event = document.createEvent("Events");
              event.initEvent("TabSelect", true, false);
              this.mCurrentTab.dispatchEvent(event);

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

              // Adjust focus
              oldBrowser._urlbarFocused = (gURLBar && gURLBar.focused);
              do {
                // When focus is in the tab bar, retain it there.
                if (document.activeElement == oldTab) {
                  // We need to explicitly focus the new tab, because
                  // tabbox.xml does this only in some cases.
                  this.mCurrentTab.focus();
                  break;
                }

                // If there's a tabmodal prompt showing, focus it.
                if (newBrowser.hasAttribute("tabmodalPromptShowing")) {
                  let XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
                  let prompts = newBrowser.parentNode.getElementsByTagNameNS(XUL_NS, "tabmodalprompt");
                  let prompt = prompts[prompts.length - 1];
                  prompt.Dialog.setDefaultFocus();
                  break;
                }

                // Focus the location bar if it was previously focused for that tab.
                // In full screen mode, only bother making the location bar visible
                // if the tab is a blank one.
                if (newBrowser._urlbarFocused && gURLBar) {

                  // Explicitly close the popup if the URL bar retains focus
                  gURLBar.closePopup();

                  if (!window.fullScreen) {
                    gURLBar.focus();
                    break;
                  } else if (isTabEmpty(this.mCurrentTab)) {
                    focusAndSelectUrlBar();
                    break;
                  }
                }

                // If the find bar is focused, keep it focused.
                if (gFindBarInitialized &&
                    !gFindBar.hidden &&
                    gFindBar.getElement("findbar-textbox").getAttribute("focused") == "true")
                  break;

                // Otherwise, focus the content area.
                let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
                let focusFlags = fm.FLAG_NOSCROLL;

                if (!gMultiProcessBrowser) {
                  let newFocusedElement = fm.getFocusedElementForWindow(window.content, true, {});

                  // for anchors, use FLAG_SHOWRING so that it is clear what link was
                  // last clicked when switching back to that tab
                  if (newFocusedElement &&
                      (newFocusedElement instanceof HTMLAnchorElement ||
                       newFocusedElement.getAttributeNS("http://www.w3.org/1999/xlink", "type") == "simple"))
                    focusFlags |= fm.FLAG_SHOWRING;
                }
                fm.setFocus(newBrowser, focusFlags);
              } while (false);
            }

            this.tabContainer._setPositionalAttributes();

            if (!aForceUpdate)
              TelemetryStopwatch.finish("FX_TAB_SWITCH_UPDATE_MS");
          ]]>
        </body>
      </method>

      <method name="_tabAttrModified">
        <parameter name="aTab"/>
        <body><![CDATA[
          if (aTab.closing)
            return;

          // 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="setTabTitleLoading">
        <parameter name="aTab"/>
        <body>
          <![CDATA[
            aTab.label = this.mStringBundle.getString("tabs.connecting");
            aTab.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 && !isBlankPageURL(title)) {
                // 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.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.emptyTabTitle");
            }

            if (aTab.label == title &&
                aTab.crop == crop)
              return false;

            aTab.label = title;
            aTab.crop = crop;
            this._tabAttrModified(aTab);

            if (aTab.selected)
              this.updateTitlebar();

            return true;
          ]]>
        </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 aFromExternal;
            var aRelatedToCurrent;
            var aIsUTF8;
            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;
              aFromExternal         = params.fromExternal;
              aRelatedToCurrent     = params.relatedToCurrent;
              aIsUTF8               = params.isUTF8;
            }

            var bgLoad = (aLoadInBackground != null) ? aLoadInBackground :
                         Services.prefs.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,
                                  fromExternal: aFromExternal,
                                  relatedToCurrent: aRelatedToCurrent,
                                  isUTF8: aIsUTF8});
            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 multiple = aURIs.length > 1;
          var owner = multiple || 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, skipAnimation: multiple});

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

          if (!aLoadInBackground) {
            if (firstTabAdded) {
              // .selectedTab setter focuses the content area
              this.selectedTab = firstTabAdded;
            }
            else
              this.selectedBrowser.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[
            const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
            var aFromExternal;
            var aRelatedToCurrent;
            var aSkipAnimation;
            var aIsUTF8;
            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;
              aFromExternal         = params.fromExternal;
              aRelatedToCurrent     = params.relatedToCurrent;
              aSkipAnimation        = params.skipAnimation;
              aIsUTF8               = params.isUTF8;
            }

            // 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(NS_XUL, "tab");

            var uriIsAboutBlank = !aURI || aURI == "about:blank";

            if (!aURI || isBlankPageURL(aURI))
              t.setAttribute("label", this.mStringBundle.getString("tabs.emptyTabTitle"));
            else
              t.setAttribute("label", aURI);

            t.setAttribute("crop", "end");
            t.setAttribute("onerror", "this.removeAttribute('image');");
            t.className = "tabbrowser-tab";

            this.tabContainer._unlockTabSizing();

            // When overflowing, new tabs are scrolled into view smoothly, which
            // doesn't go well together with the width transition. So we skip the
            // transition in that case.
            let animate = !aSkipAnimation &&
                          this.tabContainer.getAttribute("overflow") != "true" &&
                          Services.prefs.getBoolPref("browser.tabs.animate");
            if (!animate) {
              t.setAttribute("fadein", "true");
              setTimeout(function (tabContainer) {
                tabContainer._handleNewTab(t);
              }, 0, this.tabContainer);
            }

            // invalidate caches
            this._browsers = null;
            this._visibleTabs = null;

            this.tabContainer.appendChild(t);

            // 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 (Services.prefs.getPrefType("browser.tabs.remote") == Services.prefs.PREF_BOOL &&
                Services.prefs.getBoolPref("browser.tabs.remote")) {
              b.setAttribute("remote", "true");
            }

            if (window.gShowPageResizers && document.getElementById("addon-bar").collapsed &&
                window.windowState == window.STATE_NORMAL) {
              b.setAttribute("showresizer", "true");
            }

            if (this.hasAttribute("autocompletepopup"))
              b.setAttribute("autocompletepopup", this.getAttribute("autocompletepopup"));
            b.setAttribute("autoscrollpopup", this._autoScrollPopup.id);

            // Create the browserStack container
            var stack = document.createElementNS(NS_XUL, "stack");
            stack.className = "browserStack";
            stack.appendChild(b);
            stack.setAttribute("flex", "1");

            // Create the browserContainer
            var browserContainer = document.createElementNS(NS_XUL, "vbox");
            browserContainer.className = "browserContainer";
            browserContainer.appendChild(stack);
            browserContainer.setAttribute("flex", "1");

            // Create the sidebar container
            var browserSidebarContainer = document.createElementNS(NS_XUL,
                                                                   "hbox");
            browserSidebarContainer.className = "browserSidebarContainer";
            browserSidebarContainer.appendChild(browserContainer);
            browserSidebarContainer.setAttribute("flex", "1");

            // Add the Message and the Browser to the box
            var notificationbox = document.createElementNS(NS_XUL,
                                                           "notificationbox");
            notificationbox.setAttribute("flex", "1");
            notificationbox.appendChild(browserSidebarContainer);

            var position = this.tabs.length - 1;
            var uniqueId = "panel" + Date.now() + position;
            notificationbox.id = uniqueId;
            t.linkedPanel = uniqueId;
            t.linkedBrowser = b;
            t._tPos = position;
            this.tabContainer._setPositionalAttributes();

            // Prevent the superfluous initial load of a blank document
            // if we're going to load something other than about:blank.
            if (!uriIsAboutBlank) {
              b.setAttribute("nodefaultsrc", "true");
            }

            // NB: this appendChild call causes us to run constructors for the
            // browser element, which fires off a bunch of notifications. Some
            // of those notifications can cause code to run that inspects our
            // state, so it is important that the tab element is fully
            // initialized by this point.
            this.mPanelContainer.appendChild(notificationbox);

            this.tabContainer.updateVisibility();

            // wire up a progress listener for the new browser object.
            var tabListener = this.mTabProgressListener(t, b, uriIsAboutBlank);
            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;
            b.droppedLinkHandler = handleDroppedLink;

            // If we just created a new tab that loads the default
            // newtab url, swap in a preloaded page if possible.
            // Do nothing if we're a private window.
            let docShellsSwapped = false;
            if (aURI == BROWSER_NEW_TAB_URL &&
                !PrivateBrowsingUtils.isWindowPrivate(window)) {
              docShellsSwapped = gBrowserNewTabPreloader.newTab(t);
            }

            // 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 we didn't swap docShells with a preloaded browser
            // then let's just continue loading the page normally.
            if (!docShellsSwapped && !uriIsAboutBlank) {
              // pretend the user typed this so it'll be available till
              // the document successfully loads
              if (aURI && gInitialPages.indexOf(aURI) == -1)
                b.userTypedValue = aURI;

              let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
              if (aAllowThirdPartyFixup)
                flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
              if (aFromExternal)
                flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL;
              if (aIsUTF8)
                flags |= Ci.nsIWebNavigation.LOAD_FLAGS_URI_IS_UTF8;
              try {
                b.loadURIWithFlags(aURI, flags, aReferrerURI, aCharset, aPostData);
              } catch (ex) {
                Cu.reportError(ex);
              }
            }

            // We start our browsers out as inactive, and then maintain
            // activeness in the tab switcher.
            b.docShellIsActive = false;

            // 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) &&
                Services.prefs.getBoolPref("browser.tabs.insertRelatedAfterCurrent")) {
              let newTabPos = (this._lastRelatedTab ||
                               this.selectedTab)._tPos + 1;
              if (this._lastRelatedTab)
                this._lastRelatedTab.owner = null;
              else
                t.owner = this.selectedTab;
              this.moveTabTo(t, newTabPos);
              this._lastRelatedTab = t;
            }

            if (animate) {
              mozRequestAnimationFrame(function () {
                this.tabContainer._handleTabTelemetryStart(t, aURI);

                // kick the animation off
                t.setAttribute("fadein", "true");

                // This call to adjustTabstrip is redundant but needed so that
                // when opening a second tab, the first tab's close buttons
                // appears immediately rather than when the transition ends.
                if (this.tabs.length - this._removingTabs.length == 2)
                  this.tabContainer.adjustTabstrip();
              }.bind(this));
            }

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

      <method name="warnAboutClosingTabs">
      <parameter name="aCloseTabs"/>
      <parameter name="aTab"/>
      <body>
        <![CDATA[
          var tabsToClose;
          switch (aCloseTabs) {
            case this.closingTabsEnum.ALL:
              tabsToClose = this.tabs.length - this._removingTabs.length -
                            gBrowser._numPinnedTabs;
              break;
            case this.closingTabsEnum.OTHER:
              tabsToClose = this.visibleTabs.length - 1 - gBrowser._numPinnedTabs;
              break;
            case this.closingTabsEnum.TO_END:
              if (!aTab)
                throw new Error("Required argument missing: aTab");

              tabsToClose = this.getTabsToTheEndFrom(aTab).length;
              break;
            default:
              throw new Error("Invalid argument: " + aCloseTabs);
          }

          if (tabsToClose <= 1)
            return true;

          const pref = aCloseTabs == this.closingTabsEnum.ALL ?
                       "browser.tabs.warnOnClose" : "browser.tabs.warnOnCloseOtherTabs";
          var shouldPrompt = Services.prefs.getBoolPref(pref);
          if (!shouldPrompt)
            return true;

          var ps = Services.prompt;

          // default to true: if it were false, we wouldn't get this far
          var warnOnClose = { value: true };
          var bundle = this.mStringBundle;

          // 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 =
            ps.confirmEx(window,
                         bundle.getString("tabs.closeWarningTitle"),
                         bundle.getFormattedString("tabs.closeWarningMultipleTabs",
                                                   [tabsToClose]),
                         (ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_0)
                         + (ps.BUTTON_TITLE_CANCEL * ps.BUTTON_POS_1),
                         bundle.getString("tabs.closeButtonMultiple"),
                         null, null,
                         aCloseTabs == this.closingTabsEnum.ALL ?
                           bundle.getString("tabs.closeWarningPromptMe") : null,
                         warnOnClose);
          var reallyClose = (buttonPressed == 0);

          // don't set the pref unless they press OK and it's false
          if (aCloseTabs == this.closingTabsEnum.ALL && reallyClose && !warnOnClose.value)
            Services.prefs.setBoolPref(pref, false);

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

      <method name="getTabsToTheEndFrom">
        <parameter name="aTab"/>
        <body>
          <![CDATA[
            var tabsToEnd = [];
            let tabs = this.visibleTabs;
            for (let i = tabs.length - 1; tabs[i] != aTab && i >= 0; --i) {
              tabsToEnd.push(tabs[i]);
            }
            return tabsToEnd.reverse();
          ]]>
        </body>
      </method>

      <method name="removeTabsToTheEndFrom">
        <parameter name="aTab"/>
        <body>
          <![CDATA[
            if (this.warnAboutClosingTabs(this.closingTabsEnum.TO_END, aTab)) {
              let tabs = this.getTabsToTheEndFrom(aTab);
              for (let i = tabs.length - 1; i >= 0; --i) {
                this.removeTab(tabs[i], {animate: true});
              }
            }
          ]]>
        </body>
      </method>

      <method name="removeAllTabsBut">
        <parameter name="aTab"/>
        <body>
          <![CDATA[
            if (aTab.pinned)
              return;

            if (this.warnAboutClosingTabs(this.closingTabsEnum.OTHER)) {
              let tabs = this.visibleTabs;
              this.selectedTab = aTab;

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

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

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

      <method name="removeTab">
        <parameter name="aTab"/>
        <parameter name="aParams"/>
        <body>
          <![CDATA[
            if (aParams) {
              var animate = aParams.animate;
              var byMouse = aParams.byMouse;
            }

            // Handle requests for synchronously removing an already
            // asynchronously closing tab.
            if (!animate &&
                aTab.closing) {
              this._endRemoveTab(aTab);
              return;
            }

            var isLastTab = (this.tabs.length - this._removingTabs.length == 1);

            if (!this._beginRemoveTab(aTab, false, null, true))
              return;

            if (!aTab.pinned && !aTab.hidden && aTab._fullyOpen && byMouse)
              this.tabContainer._lockTabSizing(aTab);
            else
              this.tabContainer._unlockTabSizing();

            if (!animate /* the caller didn't opt in */ ||
                isLastTab ||
                aTab.pinned ||
                aTab.hidden ||
                this._removingTabs.length > 3 /* don't want lots of concurrent animations */ ||
                aTab.getAttribute("fadein") != "true" /* fade-in transition hasn't been triggered yet */ ||
                window.getComputedStyle(aTab).maxWidth == "0.1px" /* fade-in transition hasn't moved yet */ ||
                !Services.prefs.getBoolPref("browser.tabs.animate")) {
              this._endRemoveTab(aTab);
              return;
            }

            this.tabContainer._handleTabTelemetryStart(aTab);

            this._blurTab(aTab);
            aTab.style.maxWidth = ""; // ensure that fade-out transition happens
            aTab.removeAttribute("fadein");

            if (this.tabs.length - this._removingTabs.length == 1) {
              // The second tab just got closed and we will end up with a single
              // one. Remove the first tab's close button immediately (if needed)
              // rather than after the tabclose animation ends.
              this.tabContainer.adjustTabstrip();
            }

            setTimeout(function (tab, tabbrowser) {
              if (tab.parentNode &&
                  window.getComputedStyle(tab).maxWidth == "0.1px") {
                NS_ASSERT(false, "Giving up waiting for the tab closing animation to finish (bug 608589)");
                tabbrowser._endRemoveTab(tab);
              }
            }, 3000, aTab, this);
          ]]>
        </body>
      </method>

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

      <method name="_beginRemoveTab">
        <parameter name="aTab"/>
        <parameter name="aTabWillBeMoved"/>
        <parameter name="aCloseWindowWithLastTab"/>
        <parameter name="aCloseWindowFastpath"/>
        <body>
          <![CDATA[
            if (aTab.closing ||
                aTab._pendingPermitUnload ||
                this._windowIsClosing)
              return false;

            var browser = this.getBrowserForTab(aTab);

            if (!aTabWillBeMoved) {
              let ds = browser.docShell;
              if (ds && ds.contentViewer) {
                // We need to block while calling permitUnload() because it
                // processes the event queue and may lead to another removeTab()
                // call before permitUnload() even returned.
                aTab._pendingPermitUnload = true;
                let permitUnload = ds.contentViewer.permitUnload();
                delete aTab._pendingPermitUnload;

                if (!permitUnload)
                  return false;
              }
            }

            var closeWindow = false;
            var newTab = false;
            if (this.tabs.length - this._removingTabs.length == 1) {
              closeWindow = aCloseWindowWithLastTab != null ? aCloseWindowWithLastTab :
                            !window.toolbar.visible ||
                              this.tabContainer._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, window.warnAboutClosingWindow)))
                return null;

              newTab = true;
            }

            aTab.closing = true;
            this._removingTabs.push(aTab);
            this._visibleTabs = null; // invalidate cache
            if (newTab)
              this.addTab(BROWSER_NEW_TAB_URL, {skipAnimation: true});
            else
              this.tabContainer.updateVisibility();

            // 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);

            if (!gMultiProcessBrowser) {
              // Prevent this tab from showing further dialogs, since we're closing it
              var windowUtils = browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).
                                getInterface(Ci.nsIDOMWindowUtils);
              windowUtils.preventFurtherDialogs();
            }

            // 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();

            if (browser.registeredOpenURI && !aTabWillBeMoved) {
              this._placesAutocomplete.unregisterOpenPage(browser.registeredOpenURI);
              delete browser.registeredOpenURI;
            }

            // 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.tabs, 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;
            });

            aTab._endRemoveArgs = [closeWindow, newTab];
            return true;
          ]]>
        </body>
      </method>

      <method name="_endRemoveTab">
        <parameter name="aTab"/>
        <body>
          <![CDATA[
            if (!aTab || !aTab._endRemoveArgs)
              return;

            var [aCloseWindow, aNewTab] = aTab._endRemoveArgs;
            aTab._endRemoveArgs = null;

            if (this._windowIsClosing) {
              aCloseWindow = false;
              aNewTab = false;
            }

            this._lastRelatedTab = null;

            // update the UI early for responsiveness
            aTab.collapsed = true;
            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]);
            } 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;

            var wasPinned = aTab.pinned;

            // 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.tabs.length; i++)
              this.tabs[i]._tPos = i;

            if (!this._windowIsClosing) {
              if (wasPinned)
                this.tabContainer._positionPinnedTabs();

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

              setTimeout(function(tabs) {
                tabs._lastTabClosedByMouse = false;
              }, 0, this.tabContainer);
            }

            // update tab positional properties and attributes
            this.selectedTab._selected = true;
            this.tabContainer._setPositionalAttributes();

            // Removing the panel requires fixing up selectedPanel immediately
            // (see below), which would be hindered by the potentially expensive
            // browser removal. So we remove the browser and the panel in two
            // steps.

            var panel = this.getNotificationBox(browser);

            // 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.).
            browser.parentNode.removeChild(browser);

            // Release the browser in case something is erroneously holding a
            // reference to the tab after its removal.
            aTab.linkedBrowser = null;

            // As the browser 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) {
              let selectedPanel = this.mTabBox.selectedPanel;

              this.mPanelContainer.removeChild(panel);

              // Under the hood, a selectedIndex attribute controls which panel
              // is displayed. Removing a panel A which precedes the selected
              // panel B makes selectedIndex point to the panel next to B. We
              // need to explicitly preserve B as the selected panel.
              this.mTabBox.selectedPanel = selectedPanel;
            }

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

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

            if (aTab.owner &&
                !aTab.owner.hidden &&
                !aTab.owner.closing &&
                Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose")) {
              this.selectedTab = aTab.owner;
              return;
            }

            // Switch to a visible tab unless there aren't any others remaining
            let remainingTabs = this.visibleTabs;
            let numTabs = remainingTabs.length;
            if (numTabs == 0 || numTabs == 1 && remainingTabs[0] == aTab) {
              remainingTabs = Array.filter(this.tabs, function(tab) {
                return !tab.closing;
              }, this);
            }

            // Try to find a remaining tab that comes after the given tab
            var tab = aTab;
            do {
              tab = tab.nextSibling;
            } while (tab && remainingTabs.indexOf(tab) == -1);

            if (!tab) {
              tab = aTab;

              do {
                tab = tab.previousSibling;
              } while (tab && remainingTabs.indexOf(tab) == -1);
            }

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

      <method name="swapNewTabWithBrowser">
        <parameter name="aNewTab"/>
        <parameter name="aBrowser"/>
        <body>
          <![CDATA[
            // The browser must be standalone.
            if (aBrowser.getTabBrowser())
              throw Cr.NS_ERROR_INVALID_ARG;

            // The tab is definitely not loading.
            aNewTab.removeAttribute("busy");
            if (aNewTab.selected) {
              this.mIsBusy = false;
            }

            this._swapBrowserDocShells(aNewTab, aBrowser);

            // Update the new tab's title.
            this.setTabTitle(aNewTab);

            if (aNewTab.selected) {
              this.updateCurrentBrowser(true);
            }
          ]]>
        </body>
      </method>

      <method name="swapBrowsersAndCloseOther">
        <parameter name="aOurTab"/>
        <parameter name="aOtherTab"/>
        <body>
          <![CDATA[
            // Do not allow transfering a private tab to a non-private window
            // and vice versa.
            if (PrivateBrowsingUtils.isWindowPrivate(window) !=
                PrivateBrowsingUtils.isWindowPrivate(aOtherTab.ownerDocument.defaultView))
              return;

            // That's gBrowser for the other window, not the tab's browser!
            var remoteBrowser = aOtherTab.ownerDocument.defaultView.gBrowser;
            var isPending = aOtherTab.hasAttribute("pending");

            // 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.
            if (!remoteBrowser._beginRemoveTab(aOtherTab, true, true))
              return;

            let ourBrowser = this.getBrowserForTab(aOurTab);
            let otherBrowser = aOtherTab.linkedBrowser;

            // If the other tab is pending (i.e. has not been restored, yet)
            // then do not switch docShells but retrieve the other tab's state
            // and apply it to our tab.
            if (isPending) {
              let ss = Cc["@mozilla.org/browser/sessionstore;1"]
                         .getService(Ci.nsISessionStore)
              ss.setTabState(aOurTab, ss.getTabState(aOtherTab));

              // Make sure to unregister any open URIs.
              this._swapRegisteredOpenURIs(ourBrowser, otherBrowser);
            } else {
              // Workarounds for bug 458697
              // Icon might have been set on DOMLinkAdded, don't override that.
              if (!ourBrowser.mIconURL && otherBrowser.mIconURL)
                this.setIcon(aOurTab, otherBrowser.mIconURL);
              var isBusy = aOtherTab.hasAttribute("busy");
              if (isBusy) {
                aOurTab.setAttribute("busy", "true");
                this._tabAttrModified(aOurTab);
                if (aOurTab.selected)
                  this.mIsBusy = true;
              }

              this._swapBrowserDocShells(aOurTab, otherBrowser);
            }

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

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

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

      <method name="_swapBrowserDocShells">
        <parameter name="aOurTab"/>
        <parameter name="aOtherBrowser"/>
        <body>
          <![CDATA[
            // Unhook our progress listener
            let index = aOurTab._tPos;
            const filter = this.mTabFilters[index];
            let tabListener = this.mTabListeners[index];
            let ourBrowser = this.getBrowserForTab(aOurTab);
            ourBrowser.webProgress.removeProgressListener(filter);
            filter.removeProgressListener(tabListener);

            // Make sure to unregister any open URIs.
            this._swapRegisteredOpenURIs(ourBrowser, aOtherBrowser);

            // Swap the docshells
            ourBrowser.swapDocShells(aOtherBrowser);

            // Restore the progress listener
            this.mTabListeners[index] = tabListener =
              this.mTabProgressListener(aOurTab, ourBrowser, false);

            const notifyAll = Ci.nsIWebProgress.NOTIFY_ALL;
            filter.addProgressListener(tabListener, notifyAll);
            ourBrowser.webProgress.addProgressListener(filter, notifyAll);
          ]]>
        </body>
      </method>

      <method name="_swapRegisteredOpenURIs">
        <parameter name="aOurBrowser"/>
        <parameter name="aOtherBrowser"/>
        <body>
          <![CDATA[
            // If the current URI is registered as open remove it from the list.
            if (aOurBrowser.registeredOpenURI) {
              this._placesAutocomplete.unregisterOpenPage(aOurBrowser.registeredOpenURI);
              delete aOurBrowser.registeredOpenURI;
            }

            // If the other/new URI is registered as open then copy it over.
            if (aOtherBrowser.registeredOpenURI) {
              aOurBrowser.registeredOpenURI = aOtherBrowser.registeredOpenURI;
              delete aOtherBrowser.registeredOpenURI;
            }
          ]]>
        </body>
      </method>

      <method name="reloadAllTabs">
        <body>
          <![CDATA[
            let tabs = this.visibleTabs;
            let l = tabs.length;
            for (var i = 0; i < l; i++) {
              try {
                this.getBrowserForTab(tabs[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="addProgressListener">
        <parameter name="aListener"/>
        <body>
          <![CDATA[
            if (arguments.length != 1) {
              Components.utils.reportError("gBrowser.addProgressListener was " +
                                           "called with a second argument, " +
                                           "which is not supported. See bug " +
                                           "608628.");
            }

            this.mProgressListeners.push(aListener);
          ]]>
        </body>
      </method>

      <method name="removeProgressListener">
        <parameter name="aListener"/>
        <body>
          <![CDATA[
            this.mProgressListeners =
              this.mProgressListeners.filter(function (l) l != aListener);
         ]]>
        </body>
      </method>

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

      <method name="removeTabsProgressListener">
        <parameter name="aListener"/>
        <body>
        <![CDATA[
          this.mTabsProgressListeners =
            this.mTabsProgressListeners.filter(function (l) l != aListener);
        ]]>
        </body>
      </method>

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

      <method name="showOnlyTheseTabs">
        <parameter name="aTabs"/>
        <body>
        <![CDATA[
          Array.forEach(this.tabs, function(tab) {
            if (aTabs.indexOf(tab) == -1)
              this.hideTab(tab);
            else
              this.showTab(tab);
          }, this);

          this.tabContainer._handleTabSelect(false);
        ]]>
        </body>
      </method>

      <method name="showTab">
        <parameter name="aTab"/>
        <body>
        <![CDATA[
          if (aTab.hidden) {
            aTab.removeAttribute("hidden");
            this._visibleTabs = null; // invalidate cache

            this.tabContainer.adjustTabstrip();

            this.tabContainer._setPositionalAttributes();

            let event = document.createEvent("Events");
            event.initEvent("TabShow", true, false);
            aTab.dispatchEvent(event);
          }
        ]]>
        </body>
      </method>

      <method name="hideTab">
        <parameter name="aTab"/>
        <body>
        <![CDATA[
          if (!aTab.hidden && !aTab.pinned && !aTab.selected &&
              !aTab.closing) {
            aTab.setAttribute("hidden", "true");
            this._visibleTabs = null; // invalidate cache

            this.tabContainer.adjustTabstrip();

            this.tabContainer._setPositionalAttributes();

            let event = document.createEvent("Events");
            event.initEvent("TabHide", true, false);
            aTab.dispatchEvent(event);
          }
        ]]>
        </body>
      </method>

      <method name="selectTabAtIndex">
        <parameter name="aIndex"/>
        <parameter name="aEvent"/>
        <body>
        <![CDATA[
          let tabs = this.visibleTabs;

          // count backwards for aIndex < 0
          if (aIndex < 0)
            aIndex += tabs.length;

          if (aIndex >= 0 && aIndex < tabs.length)
            this.selectedTab = tabs[aIndex];

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

      <property name="selectedTab">
        <getter>
          return this.mCurrentTab;
        </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.tabs, function (tab) tab.linkedBrowser));
          ]]>
        </getter>
      </property>
      <field name="_browsers">null</field>

      <!-- 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"/>
        <parameter name="aOptions"/>
        <body>
          <![CDATA[
            if (this.tabs.length == 1)
              return null;

            var options = "chrome,dialog=no,all";
            for (var name in aOptions)
              options += "," + name + "=" + aOptions[name];

            // tell a new window to take the "dropped" tab
            return window.openDialog(getBrowserURL(), "_blank", options, aTab);
          ]]>
        </body>
      </method>

      <method name="moveTabTo">
        <parameter name="aTab"/>
        <parameter name="aIndex"/>
        <body>
        <![CDATA[
          var oldPosition = aTab._tPos;
          if (oldPosition == aIndex)
            return;

          // Don't allow mixing pinned and unpinned tabs.
          if (aTab.pinned)
            aIndex = Math.min(aIndex, this._numPinnedTabs - 1);
          else
            aIndex = Math.max(aIndex, this._numPinnedTabs);
          if (oldPosition == aIndex)
            return;

          this._lastRelatedTab = null;

          this.mTabFilters.splice(aIndex, 0, this.mTabFilters.splice(aTab._tPos, 1)[0]);
          this.mTabListeners.splice(aIndex, 0, this.mTabListeners.splice(aTab._tPos, 1)[0]);

          let wasFocused = (document.activeElement == this.mCurrentTab);

          aIndex = aIndex < aTab._tPos ? aIndex: aIndex+1;
          this.mCurrentTab._selected = false;

          // invalidate caches
          this._browsers = null;
          this._visibleTabs = null;

          // 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.tabContainer.insertBefore(aTab, this.tabs.item(aIndex));

          for (let i = 0; i < this.tabs.length; i++) {
            this.tabs[i]._tPos = i;
            this.tabs[i]._selected = false;
          }
          this.mCurrentTab._selected = true;

          if (wasFocused)
            this.mCurrentTab.focus();

          this.tabContainer._handleTabSelect(false);

          if (aTab.pinned)
            this.tabContainer._positionPinnedTabs();

          this.tabContainer._setPositionalAttributes();

          var evt = document.createEvent("UIEvents");
          evt.initUIEvent("TabMove", true, false, window, oldPosition);
          aTab.dispatchEvent(evt);
        ]]>
        </body>
      </method>

      <method name="moveTabForward">
        <body>
          <![CDATA[
            let nextTab = this.mCurrentTab.nextSibling;
            while (nextTab && nextTab.hidden)
              nextTab = nextTab.nextSibling;

            if (nextTab)
              this.moveTabTo(this.mCurrentTab, nextTab._tPos);
            else if (this.arrowKeysShouldWrap)
              this.moveTabToStart();
          ]]>
        </body>
      </method>

      <method name="moveTabBackward">
        <body>
          <![CDATA[
            let previousTab = this.mCurrentTab.previousSibling;
            while (previousTab && previousTab.hidden)
              previousTab = previousTab.previousSibling;

            if (previousTab)
              this.moveTabTo(this.mCurrentTab, previousTab._tPos);
            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);
          ]]>
        </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);
          ]]>
        </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[
            return Cc["@mozilla.org/browser/sessionstore;1"]
                     .getService(Ci.nsISessionStore)
                     .duplicateTab(window, aTab);
          ]]>
        </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="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="_handleKeyEvent">
        <parameter name="aEvent"/>
        <body><![CDATA[
          if (!aEvent.isTrusted) {
            // Don't let untrusted events mess with tabs.
            return;
          }

          if (aEvent.altKey)
            return;

          if (aEvent.ctrlKey && aEvent.shiftKey && !aEvent.metaKey) {
            switch (aEvent.keyCode) {
              case aEvent.DOM_VK_PAGE_UP:
                this.moveTabBackward();
                aEvent.stopPropagation();
                aEvent.preventDefault();
                return;
              case aEvent.DOM_VK_PAGE_DOWN:
                this.moveTabForward();
                aEvent.stopPropagation();
                aEvent.preventDefault();
                return;
            }
          }

          // We need to take care of FAYT-watching as long as the findbar
          // isn't initialized.  The checks on aEvent are copied from
          // _shouldFastFind (see findbar.xml).
          if (!gFindBarInitialized &&
              !(aEvent.ctrlKey || aEvent.metaKey) &&
              !aEvent.defaultPrevented) {
            let charCode = aEvent.charCode;
            if (charCode) {
              let char = String.fromCharCode(charCode);
              if (char == "'" || char == "/" ||
                  Services.prefs.getBoolPref("accessibility.typeaheadfind")) {
                gFindBar._onBrowserKeypress(aEvent);
                return;
              }
            }
          }

          if (aEvent.ctrlKey && !aEvent.shiftKey && !aEvent.metaKey &&
              aEvent.keyCode == KeyEvent.DOM_VK_F4 &&
              !this.mCurrentTab.pinned) {
            this.removeCurrentTab({animate: true});
            aEvent.stopPropagation();
            aEvent.preventDefault();
          }
        ]]></body>
      </method>

      <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 tab = document.tooltipNode;
          if (tab.localName != "tab") {
            event.preventDefault();
            return;
          }
          event.target.setAttribute("label", tab.mOverCloseButton ?
                                             tab.getAttribute("closetabtext") :
                                             tab.getAttribute("label"));
        ]]></body>
      </method>

      <method name="handleEvent">
        <parameter name="aEvent"/>
        <body><![CDATA[
          switch (aEvent.type) {
            case "keypress":
              this._handleKeyEvent(aEvent);
              break;
            case "sizemodechange":
              if (aEvent.target == window) {
                this.mCurrentBrowser.docShellIsActive =
                  (window.windowState != window.STATE_MINIMIZED);
              }
              break;
          }
        ]]></body>
      </method>

      <method name="receiveMessage">
        <parameter name="aMessage"/>
        <body><![CDATA[
          let json = aMessage.json;
          let browser = aMessage.target;

          switch (aMessage.name) {
            case "DOMTitleChanged":
              let tab = this._getTabForBrowser(browser);
              if (!tab)
                return;
              let titleChanged = this.setTabTitle(tab);
              if (titleChanged && !tab.selected && !tab.hasAttribute("busy"))
                tab.setAttribute("titlechanged", "true");
          }
        ]]></body>
      </method>

      <constructor>
        <![CDATA[
          let browserStack = document.getAnonymousElementByAttribute(this, "anonid", "browserStack");
          this.mCurrentBrowser = document.getAnonymousElementByAttribute(this, "anonid", "initialBrowser");
          if (Services.prefs.getBoolPref("browser.tabs.remote")) {
            browserStack.removeChild(this.mCurrentBrowser);
            this.mCurrentBrowser.setAttribute("remote", true);
            browserStack.appendChild(this.mCurrentBrowser);
          }

          this.mCurrentTab = this.tabContainer.firstChild;
          document.addEventListener("keypress", this, false);
          window.addEventListener("sizemodechange", this, false);

          var uniqueId = "panel" + Date.now();
          this.mPanelContainer.childNodes[0].id = uniqueId;
          this.mCurrentTab.linkedPanel = uniqueId;
          this.mCurrentTab._tPos = 0;
          this.mCurrentTab._fullyOpen = true;
          this.mCurrentTab.linkedBrowser = this.mCurrentBrowser;

          // 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);
          this.mCurrentBrowser.droppedLinkHandler = handleDroppedLink;
          this.updateWindowResizers();

          // Hook up the event listeners to the first browser
          var tabListener = this.mTabProgressListener(this.mCurrentTab, this.mCurrentBrowser, true);
          const nsIWebProgress = Components.interfaces.nsIWebProgress;
          const filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
                                   .createInstance(nsIWebProgress);
          filter.addProgressListener(tabListener, nsIWebProgress.NOTIFY_ALL);
          this.mTabListeners[0] = tabListener;
          this.mTabFilters[0] = filter;

          try {
            // We assume this can only fail because mCurrentBrowser's docShell
            // hasn't been created, yet. This may be caused by code accessing
            // gBrowser before the window has finished loading.
            this._addProgressListenerForInitialTab();
          } catch (e) {
            // The binding was constructed too early, wait until the initial
            // tab's document is ready, then add the progress listener.
            this._waitForInitialContentDocument();
          }

          this.style.backgroundColor =
            Services.prefs.getBoolPref("browser.display.use_system_colors") ?
              "-moz-default-background-color" :
              Services.prefs.getCharPref("browser.display.background_color");

          if (Services.prefs.getBoolPref("browser.tabs.remote"))
            messageManager.addMessageListener("DOMTitleChanged", this);
        ]]>
      </constructor>

      <method name="_addProgressListenerForInitialTab">
        <body><![CDATA[
          this.webProgress.addProgressListener(this.mTabFilters[0], Ci.nsIWebProgress.NOTIFY_ALL);
        ]]></body>
      </method>

      <method name="_waitForInitialContentDocument">
        <body><![CDATA[
          let obs = (subject, topic) => {
            if (this.browsers[0].contentWindow == subject) {
              Services.obs.removeObserver(obs, topic);
              this._addProgressListenerForInitialTab();
            }
          };

          // We use content-document-global-created as an approximation for
          // "docShell is initialized". We can do this because in the
          // mTabProgressListener we care most about the STATE_STOP notification
          // that will reset mBlank. That means it's important to at least add
          // the progress listener before the initial about:blank load stops
          // if we can't do it before the load starts.
          Services.obs.addObserver(obs, "content-document-global-created", false);
        ]]></body>
      </method>

      <destructor>
        <![CDATA[
          for (var i = 0; i < this.mTabListeners.length; ++i) {
            let browser = this.getBrowserAtIndex(i);
            if (browser.registeredOpenURI) {
              this._placesAutocomplete.unregisterOpenPage(browser.registeredOpenURI);
              delete browser.registeredOpenURI;
            }
            browser.webProgress.removeProgressListener(this.mTabFilters[i]);
            this.mTabFilters[i].removeProgressListener(this.mTabListeners[i]);
            this.mTabFilters[i] = null;
            this.mTabListeners[i].destroy();
            this.mTabListeners[i] = null;
          }
          document.removeEventListener("keypress", this, false);
          window.removeEventListener("sizemodechange", this, false);

          if (Services.prefs.getBoolPref("browser.tabs.remote"))
            messageManager.removeMessageListener("DOMTitleChanged", this);
        ]]>
      </destructor>

      <!-- Deprecated stuff, implemented for backwards compatibility. -->
      <method name="enterTabbedMode">
        <body>
          Application.console.log("enterTabbedMode is an obsolete method and " +
                                  "will be removed in a future release.");
        </body>
      </method>
      <field name="mTabbedMode" readonly="true">true</field>
      <method name="setStripVisibilityTo">
        <parameter name="aShow"/>
        <body>
          this.tabContainer.visible = aShow;
        </body>
      </method>
      <method name="getStripVisibility">
        <body>
          return this.tabContainer.visible;
        </body>
      </method>
      <property name="mContextTab" readonly="true"
                onget="return TabContextMenu.contextTab;"/>
      <property name="mPrefs" readonly="true"
                onget="return Services.prefs;"/>
      <property name="mTabContainer" readonly="true"
                onget="return this.tabContainer;"/>
      <property name="mTabs" readonly="true"
                onget="return this.tabs;"/>
      <!--
        - Compatibility hack: several extensions depend on this property to
        - access the tab context menu or tab container, so keep that working for
        - now. Ideally we can remove this once extensions are using
        - tabbrowser.tabContextMenu and tabbrowser.tabContainer directly.
        -->
      <property name="mStrip" readonly="true">
        <getter>
        <![CDATA[
          return ({
            self: this,
            childNodes: [null, this.tabContextMenu, this.tabContainer],
            firstChild: { nextSibling: this.tabContextMenu },
            getElementsByAttribute: function (attr, attrValue) {
              if (attr == "anonid" && attrValue == "tabContextMenu")
                return [this.self.tabContextMenu];
              return [];
            },
            // Also support adding event listeners (forward to the tab container)
            addEventListener: function (a,b,c) { this.self.tabContainer.addEventListener(a,b,c); },
            removeEventListener: function (a,b,c) { this.self.tabContainer.removeEventListener(a,b,c); }
          });
        ]]>
        </getter>
      </property>
    </implementation>

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

          if (this.tabs.length == 1)
            return;

          var tab = this._getTabForContentWindow(event.target);
          if (tab) {
            this.removeTab(tab);
            event.preventDefault();
          }
        ]]>
      </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.
          this.selectedTab = this._getTabForContentWindow(event.target.top);
        ]]>
      </handler>
      <handler event="DOMTitleChanged">
        <![CDATA[
          if (!event.isTrusted)
            return;

          var contentWin = event.target.defaultView;
          if (contentWin != contentWin.top)
            return;

          var tab = this._getTabForContentWindow(contentWin);
          var titleChanged = this.setTabTitle(tab);
          if (titleChanged && !tab.selected && !tab.hasAttribute("busy"))
            tab.setAttribute("titlechanged", "true");
        ]]>
      </handler>
    </handlers>
  </binding>

  <binding id="tabbrowser-tabbox"
           extends="chrome://global/content/bindings/tabbox.xml#tabbox">
    <implementation>
      <property name="tabs" readonly="true"
                onget="return document.getBindingParent(this).tabContainer;"/>
    </implementation>
  </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 Array.filter(document.getBindingParent(this).childNodes,
                              this._canScrollToElement, this);
        ]]></body>
      </method>
      <method name="_canScrollToElement">
        <parameter name="tab"/>
        <body><![CDATA[
          return !tab.pinned && !tab.hidden;
        ]]></body>
      </method>
    </implementation>

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

        var tabs = document.getBindingParent(this);
        tabs.removeAttribute("overflow");

        if (tabs._lastTabClosedByMouse)
          tabs._expandSpacerBy(this._scrollButtonDown.clientWidth);

        tabs.tabbrowser._removingTabs.forEach(tabs.tabbrowser.removeTab,
                                              tabs.tabbrowser);

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

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

  <binding id="tabbrowser-tabs"
           extends="chrome://global/content/bindings/tabbox.xml#tabs">
    <resources>
      <stylesheet src="chrome://browser/content/tabbrowser.css"/>
    </resources>

    <content>
      <xul:hbox align="end">
        <xul:image class="tab-drop-indicator" anonid="tab-drop-indicator" collapsed="true"/>
      </xul:hbox>
      <xul:arrowscrollbox anonid="arrowscrollbox" orient="horizontal" flex="1"
                          style="min-width: 1px;"
                          clicktoscroll="true"
                          class="tabbrowser-arrowscrollbox">
        <children includes="tab"/>
        <children/>
        <xul:toolbarbutton class="tabs-newtab-button"
                           command="cmd_newNavigatorTab"
                           onclick="checkForMiddleClick(this, event);"
                           onmouseover="document.getBindingParent(this)._enterNewTab();"
                           onmouseout="document.getBindingParent(this)._leaveNewTab();"
                           tooltiptext="&newTabButton.tooltip;"/>
        <xul:spacer class="closing-tabs-spacer" anonid="closing-tabs-spacer"
                    style="width: 0;"/>
      </xul:arrowscrollbox>
    </content>

    <implementation implements="nsIDOMEventListener">
      <constructor>
        <![CDATA[
          this.mTabClipWidth = Services.prefs.getIntPref("browser.tabs.tabClipWidth");
          this.mCloseButtons = Services.prefs.getIntPref("browser.tabs.closeButtons");
          this._closeWindowWithLastTab = Services.prefs.getBoolPref("browser.tabs.closeWindowWithLastTab");

          var tab = this.firstChild;
          tab.setAttribute("label",
                           this.tabbrowser.mStringBundle.getString("tabs.emptyTabTitle"));
          tab.setAttribute("crop", "end");
          tab.setAttribute("onerror", "this.removeAttribute('image');");
          this.adjustTabstrip();

          Services.prefs.addObserver("browser.tabs.", this._prefObserver, false);
          window.addEventListener("resize", this, false);
          window.addEventListener("load", this, false);

          try {
            this._tabAnimationLoggingEnabled = Services.prefs.getBoolPref("browser.tabs.animationLogging.enabled");
          } catch (ex) {
            this._tabAnimationLoggingEnabled = false;
          }
          this._browserNewtabpageEnabled = Services.prefs.getBoolPref("browser.newtabpage.enabled");
        ]]>
      </constructor>

      <destructor>
        <![CDATA[
          Services.prefs.removeObserver("browser.tabs.", this._prefObserver);
        ]]>
      </destructor>

      <field name="tabbrowser" readonly="true">
        document.getElementById(this.getAttribute("tabbrowser"));
      </field>

      <field name="tabbox" readonly="true">
        this.tabbrowser.mTabBox;
      </field>

      <field name="contextMenu" readonly="true">
        document.getElementById("tabContextMenu");
      </field>

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

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

      <field name="_firstTab">null</field>
      <field name="_lastTab">null</field>
      <field name="_afterSelectedTab">null</field>
      <field name="_beforeHoveredTab">null</field>
      <field name="_afterHoveredTab">null</field>

      <method name="_setPositionalAttributes">
        <body><![CDATA[
          let visibleTabs = this.tabbrowser.visibleTabs;

          if (!visibleTabs.length)
            return;

          let selectedIndex = visibleTabs.indexOf(this.selectedItem);

          let lastVisible = visibleTabs.length - 1;

          if (this._afterSelectedTab)
            this._afterSelectedTab.removeAttribute("afterselected-visible");
          if (this.selectedItem.closing || selectedIndex == lastVisible) {
            this._afterSelectedTab = null;
          } else {
            this._afterSelectedTab = visibleTabs[selectedIndex + 1];
            this._afterSelectedTab.setAttribute("afterselected-visible",
                                                "true");
          }

          if (this._firstTab)
            this._firstTab.removeAttribute("first-visible-tab");
          this._firstTab = visibleTabs[0];
          this._firstTab.setAttribute("first-visible-tab", "true");
          if (this._lastTab)
            this._lastTab.removeAttribute("last-visible-tab");
          this._lastTab = visibleTabs[lastVisible];
          this._lastTab.setAttribute("last-visible-tab", "true");
        ]]></body>
      </method>

      <field name="_prefObserver"><![CDATA[({
        tabContainer: this,

        observe: function (subject, topic, data) {
          switch (data) {
            case "browser.tabs.closeButtons":
              this.tabContainer.mCloseButtons = Services.prefs.getIntPref(data);
              this.tabContainer.adjustTabstrip();
              break;
            case "browser.tabs.closeWindowWithLastTab":
              this.tabContainer._closeWindowWithLastTab = Services.prefs.getBoolPref(data);
              this.tabContainer.adjustTabstrip();
              break;
          }
        }
      });]]></field>
      <field name="_blockDblClick">false</field>

      <field name="_tabDropIndicator">
        document.getAnonymousElementByAttribute(this, "anonid", "tab-drop-indicator");
      </field>

      <field name="_dragOverDelay">350</field>
      <field name="_dragTime">0</field>

      <field name="_container" readonly="true"><![CDATA[
        this.parentNode && this.parentNode.localName == "toolbar" ? this.parentNode : this;
      ]]></field>

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

      <property name="visible"
                onget="return !this._container.collapsed;">
        <setter><![CDATA[
          if (val == this.visible &&
              this._propagatedVisibilityOnce)
            return val;

          this._container.collapsed = !val;

          this._propagateVisibility();
          this._propagatedVisibilityOnce = true;

          return val;
        ]]></setter>
      </property>

      <method name="_enterNewTab">
        <body><![CDATA[
          let visibleTabs = this.tabbrowser.visibleTabs;
          let candidate = visibleTabs[visibleTabs.length - 1];
          if (!candidate.selected) {
            this._beforeHoveredTab = candidate;
            candidate.setAttribute("beforehovered", "true");
          }
        ]]></body>
      </method>

      <method name="_leaveNewTab">
        <body><![CDATA[
          if (this._beforeHoveredTab) {
            this._beforeHoveredTab.removeAttribute("beforehovered");
            this._beforeHoveredTab = null;
          }
        ]]></body>
      </method>

      <method name="_propagateVisibility">
        <body><![CDATA[
          let visible = this.visible;

          document.getElementById("menu_closeWindow").hidden = !visible;
          document.getElementById("menu_close").setAttribute("label",
            this.tabbrowser.mStringBundle.getString(visible ? "tabs.closeTab" : "tabs.close"));

          goSetCommandEnabled("cmd_ToggleTabsOnTop", visible);

          TabsOnTop.syncUI();

          TabsInTitlebar.allowedBy("tabs-visible", visible);
        ]]></body>
      </method>

      <method name="updateVisibility">
        <body><![CDATA[
          if (this.childNodes.length - this.tabbrowser._removingTabs.length == 1)
            this.visible = window.toolbar.visible;
          else
            this.visible = true;
        ]]></body>
      </method>

      <method name="adjustTabstrip">
        <body><![CDATA[
          let numTabs = this.childNodes.length -
                        this.tabbrowser._removingTabs.length;
          // modes for tabstrip
          // 0 - button on active tab only
          // 1 - close buttons on all tabs
          // 2 - no close buttons at all
          // 3 - close button at the end of the tabstrip
          switch (this.mCloseButtons) {
          case 0:
            if (numTabs == 1 && this._closeWindowWithLastTab)
              this.setAttribute("closebuttons", "hidden");
            else
              this.setAttribute("closebuttons", "activetab");
            break;
          case 1:
            if (numTabs == 1) {
              if (this._closeWindowWithLastTab)
                this.setAttribute("closebuttons", "hidden");
              else
                this.setAttribute("closebuttons", "alltabs");
            } else if (numTabs == 2) {
              // This is an optimization to avoid layout flushes by calling
              // getBoundingClientRect() when we just opened a second tab. In
              // this case it's highly unlikely that the tab width is smaller
              // than mTabClipWidth and the tab close button obscures too much
              // of the tab's label. In the edge case of the window being too
              // narrow (or if tabClipWidth has been set to a way higher value),
              // we'll correct the 'closebuttons' attribute after the tabopen
              // animation has finished.
              this.setAttribute("closebuttons", "alltabs");
            } else {
              let tab = this.tabbrowser.visibleTabs[this.tabbrowser._numPinnedTabs];
              if (tab && tab.getBoundingClientRect().width > this.mTabClipWidth)
                this.setAttribute("closebuttons", "alltabs");
              else
                this.setAttribute("closebuttons", "activetab");
            }
            break;
          case 2:
          case 3:
            this.setAttribute("closebuttons", "never");
            break;
          }
          var tabstripClosebutton = document.getElementById("tabs-closebutton");
          if (tabstripClosebutton && tabstripClosebutton.parentNode == this._container)
            tabstripClosebutton.collapsed = this.mCloseButtons != 3;
        ]]></body>
      </method>

      <method name="_handleTabSelect">
        <parameter name="aSmoothScroll"/>
        <body><![CDATA[
          if (this.getAttribute("overflow") == "true")
            this.mTabstrip.ensureElementIsVisible(this.selectedItem, aSmoothScroll);
        ]]></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>

      <field name="_closingTabsSpacer">
        document.getAnonymousElementByAttribute(this, "anonid", "closing-tabs-spacer");
      </field>

      <field name="_tabDefaultMaxWidth">NaN</field>
      <field name="_lastTabClosedByMouse">false</field>
      <field name="_hasTabTempMaxWidth">false</field>

      <!-- Try to keep the active tab's close button under the mouse cursor -->
      <method name="_lockTabSizing">
        <parameter name="aTab"/>
        <body><![CDATA[
          var tabs = this.tabbrowser.visibleTabs;
          if (!tabs.length)
            return;

          var isEndTab = (aTab._tPos > tabs[tabs.length-1]._tPos);
          var tabWidth = aTab.getBoundingClientRect().width;

          if (!this._tabDefaultMaxWidth)
            this._tabDefaultMaxWidth =
              parseFloat(window.getComputedStyle(aTab).maxWidth);
          this._lastTabClosedByMouse = true;

          if (this.getAttribute("overflow") == "true") {
            // Don't need to do anything if we're in overflow mode and aren't scrolled
            // all the way to the right, or if we're closing the last tab.
            if (isEndTab || !this.mTabstrip._scrollButtonDown.disabled)
              return;

            // If the tab has an owner that will become the active tab, the owner will
            // be to the left of it, so we actually want the left tab to slide over.
            // This can't be done as easily in non-overflow mode, so we don't bother.
            if (aTab.owner)
              return;

            this._expandSpacerBy(tabWidth);
          } else { // non-overflow mode
            // Locking is neither in effect nor needed, so let tabs expand normally.
            if (isEndTab && !this._hasTabTempMaxWidth)
              return;

            let numPinned = this.tabbrowser._numPinnedTabs;
            // Force tabs to stay the same width, unless we're closing the last tab,
            // which case we need to let them expand just enough so that the overall
            // tabbar width is the same.
            if (isEndTab) {
              let numNormalTabs = tabs.length - numPinned;
              tabWidth = tabWidth * (numNormalTabs + 1) / numNormalTabs;
              if (tabWidth > this._tabDefaultMaxWidth)
                tabWidth = this._tabDefaultMaxWidth;
            }
            tabWidth += "px";
            for (let i = numPinned; i < tabs.length; i++) {
              let tab = tabs[i];
              tab.style.setProperty("max-width", tabWidth, "important");
              if (!isEndTab) { // keep tabs the same width
                tab.style.transition = "none";
                tab.clientTop; // flush styles to skip animation; see bug 649247
                tab.style.transition = "";
              }
            }
            this._hasTabTempMaxWidth = true;
            this.tabbrowser.addEventListener("mousemove", this, false);
            window.addEventListener("mouseout", this, false);
          }
        ]]></body>
      </method>

      <method name="_expandSpacerBy">
        <parameter name="pixels"/>
        <body><![CDATA[
          let spacer = this._closingTabsSpacer;
          spacer.style.width = parseFloat(spacer.style.width) + pixels + "px";
          this.setAttribute("using-closing-tabs-spacer", "true");
          this.tabbrowser.addEventListener("mousemove", this, false);
          window.addEventListener("mouseout", this, false);
        ]]></body>
      </method>

      <method name="_unlockTabSizing">
        <body><![CDATA[
          this.tabbrowser.removeEventListener("mousemove", this, false);
          window.removeEventListener("mouseout", this, false);

          if (this._hasTabTempMaxWidth) {
            this._hasTabTempMaxWidth = false;
            let tabs = this.tabbrowser.visibleTabs;
            for (let i = 0; i < tabs.length; i++)
              tabs[i].style.maxWidth = "";
          }

          if (this.hasAttribute("using-closing-tabs-spacer")) {
            this.removeAttribute("using-closing-tabs-spacer");
            this._closingTabsSpacer.style.width = 0;
          }
        ]]></body>
      </method>

      <field name="_lastNumPinned">0</field>
      <method name="_positionPinnedTabs">
        <body><![CDATA[
          var numPinned = this.tabbrowser._numPinnedTabs;
          var doPosition = this.getAttribute("overflow") == "true" &&
                           numPinned > 0;

          if (doPosition) {
            this.setAttribute("positionpinnedtabs", "true");

            let scrollButtonWidth = this.mTabstrip._scrollButtonDown.getBoundingClientRect().width;
            let paddingStart = this.mTabstrip.scrollboxPaddingStart;
            let width = 0;

            for (let i = numPinned - 1; i >= 0; i--) {
              let tab = this.childNodes[i];
              width += tab.getBoundingClientRect().width;
              tab.style.MozMarginStart = - (width + scrollButtonWidth + paddingStart) + "px";
            }

            this.style.MozPaddingStart = width + paddingStart + "px";

          } else {
            this.removeAttribute("positionpinnedtabs");

            for (let i = 0; i < numPinned; i++) {
              let tab = this.childNodes[i];
              tab.style.MozMarginStart = "";
            }

            this.style.MozPaddingStart = "";
          }

          if (this._lastNumPinned != numPinned) {
            this._lastNumPinned = numPinned;
            this._handleTabSelect(false);
          }
        ]]></body>
      </method>

      <method name="_animateTabMove">
        <parameter name="event"/>
        <body><![CDATA[
          let draggedTab = event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0);

          if (this.getAttribute("movingtab") != "true") {
            this.setAttribute("movingtab", "true");
            this.selectedItem = draggedTab;
          }

          if (!("animLastScreenX" in draggedTab._dragData))
            draggedTab._dragData.animLastScreenX = draggedTab._dragData.screenX;

          let screenX = event.screenX;
          if (screenX == draggedTab._dragData.animLastScreenX)
            return;

          let draggingRight = screenX > draggedTab._dragData.animLastScreenX;
          draggedTab._dragData.animLastScreenX = screenX;

          let rtl = (window.getComputedStyle(this).direction == "rtl");
          let pinned = draggedTab.pinned;
          let numPinned = this.tabbrowser._numPinnedTabs;
          let tabs = this.tabbrowser.visibleTabs
                                    .slice(pinned ? 0 : numPinned,
                                           pinned ? numPinned : undefined);
          if (rtl)
            tabs.reverse();
          let tabWidth = draggedTab.getBoundingClientRect().width;

          // Move the dragged tab based on the mouse position.

          let leftTab = tabs[0];
          let rightTab = tabs[tabs.length - 1];
          let tabScreenX = draggedTab.boxObject.screenX;
          let translateX = screenX - draggedTab._dragData.screenX;
          if (!pinned)
            translateX += this.mTabstrip.scrollPosition - draggedTab._dragData.scrollX;
          let leftBound = leftTab.boxObject.screenX - tabScreenX;
          let rightBound = (rightTab.boxObject.screenX + rightTab.boxObject.width) -
                           (tabScreenX + tabWidth);
          translateX = Math.max(translateX, leftBound);
          translateX = Math.min(translateX, rightBound);
          draggedTab.style.transform = "translateX(" + translateX + "px)";

          // Determine what tab we're dragging over.
          // * Point of reference is the center of the dragged tab. If that
          //   point touches a background tab, the dragged tab would take that
          //   tab's position when dropped.
          // * We're doing a binary search in order to reduce the amount of
          //   tabs we need to check.

          let tabCenter = tabScreenX + translateX + tabWidth / 2;
          let newIndex = -1;
          let oldIndex = "animDropIndex" in draggedTab._dragData ?
                         draggedTab._dragData.animDropIndex : draggedTab._tPos;
          let low = 0;
          let high = tabs.length - 1;
          while (low <= high) {
            let mid = Math.floor((low + high) / 2);
            if (tabs[mid] == draggedTab &&
                ++mid > high)
              break;
            let boxObject = tabs[mid].boxObject;
            let screenX = boxObject.screenX + getTabShift(tabs[mid], oldIndex);
            if (screenX > tabCenter) {
              high = mid - 1;
            } else if (screenX + boxObject.width < tabCenter) {
              low = mid + 1;
            } else {
              newIndex = tabs[mid]._tPos;
              break;
            }
          }
          if (newIndex >= oldIndex)
            newIndex++;
          if (newIndex < 0 || newIndex == oldIndex)
            return;
          draggedTab._dragData.animDropIndex = newIndex;

          // Shift background tabs to leave a gap where the dragged tab
          // would currently be dropped.

          for (let tab of tabs) {
            if (tab != draggedTab) {
              let shift = getTabShift(tab, newIndex);
              tab.style.transform = shift ? "translateX(" + shift + "px)" : "";
            }
          }

          function getTabShift(tab, dropIndex) {
            if (tab._tPos < draggedTab._tPos && tab._tPos >= dropIndex)
              return rtl ? -tabWidth : tabWidth;
            if (tab._tPos > draggedTab._tPos && tab._tPos < dropIndex)
              return rtl ? tabWidth : -tabWidth;
            return 0;
          }
        ]]></body>
      </method>

      <method name="_finishAnimateTabMove">
        <body><![CDATA[
          if (this.getAttribute("movingtab") != "true")
            return;

          for (let tab of this.tabbrowser.visibleTabs)
            tab.style.transform = "";

          this.removeAttribute("movingtab");

          this._handleTabSelect();
        ]]></body>
      </method>

      <method name="handleEvent">
        <parameter name="aEvent"/>
        <body><![CDATA[
          switch (aEvent.type) {
            case "load":
              this.updateVisibility();
              break;
            case "resize":
              if (aEvent.target != window)
                break;

              let sizemode = document.documentElement.getAttribute("sizemode");
              TabsInTitlebar.allowedBy("sizemode",
                                       sizemode == "maximized" || sizemode == "fullscreen");

              var width = this.mTabstrip.boxObject.width;
              if (width != this.mTabstripWidth) {
                this.adjustTabstrip();
                this._fillTrailingGap();
                this._handleTabSelect();
                this.mTabstripWidth = width;
              }

              this.tabbrowser.updateWindowResizers();
              break;
            case "mouseout":
              // If the "related target" (the node to which the pointer went) is not
              // a child of the current document, the mouse just left the window.
              let relatedTarget = aEvent.relatedTarget;
              if (relatedTarget && relatedTarget.ownerDocument == document)
                break;
            case "mousemove":
              if (document.getElementById("tabContextMenu").state != "open")
                this._unlockTabSizing();
              break;
          }
        ]]></body>
      </method>

      <field name="_animateElement">
        this.mTabstrip._scrollButtonDown;
      </field>

      <method name="_notifyBackgroundTab">
        <parameter name="aTab"/>
        <body><![CDATA[
          if (aTab.pinned)
            return;

          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.pinned &&
                           this.selectedItem.getBoundingClientRect();

            // Can we make both the new tab and the selected tab completely visible?
            if (!selected ||
                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>

      <method name="_getDragTargetTab">
        <parameter name="event"/>
        <body><![CDATA[
          let tab = event.target.localName == "tab" ? event.target : null;
          if (tab &&
              (event.type == "drop" || event.type == "dragover") &&
              event.dataTransfer.dropEffect == "link") {
            let boxObject = tab.boxObject;
            if (event.screenX < boxObject.screenX + boxObject.width * .25 ||
                event.screenX > boxObject.screenX + boxObject.width * .75)
              return null;
          }
          return tab;
        ]]></body>
      </method>

      <method name="_getDropIndex">
        <parameter name="event"/>
        <body><![CDATA[
          var tabs = this.childNodes;
          var tab = this._getDragTargetTab(event);
          if (window.getComputedStyle(this, null).direction == "ltr") {
            for (let i = tab ? tab._tPos : 0; i < tabs.length; i++)
              if (event.screenX < tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2)
                return i;
          } else {
            for (let i = tab ? tab._tPos : 0; i < tabs.length; i++)
              if (event.screenX > tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2)
                return i;
          }
          return tabs.length;
        ]]></body>
      </method>

      <method name="_setEffectAllowedForDataTransfer">
        <parameter name="event"/>
        <body><![CDATA[
          var dt = event.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.ownerDocument.defaultView instanceof ChromeWindow &&
                sourceNode.ownerDocument.documentElement.getAttribute("windowtype") == "navigator:browser" &&
                sourceNode.ownerDocument.defaultView.gBrowser.tabContainer == sourceNode.parentNode) {
              // Do not allow transfering a private tab to a non-private window
              // and vice versa.
              if (PrivateBrowsingUtils.isWindowPrivate(window) !=
                  PrivateBrowsingUtils.isWindowPrivate(sourceNode.ownerDocument.defaultView))
                return dt.effectAllowed = "none";

              return dt.effectAllowed = event.ctrlKey ? "copy" : "move";
            }
          }

          if (browserDragAndDrop.canDropLink(event)) {
            // Here we need to do this manually
            return dt.effectAllowed = dt.dropEffect = "link";
          }
          return dt.effectAllowed = "none";
        ]]></body>
      </method>

      <method name="_handleNewTab">
        <parameter name="tab"/>
        <body><![CDATA[
          if (tab.parentNode != this)
            return;
          tab._fullyOpen = true;

          this.adjustTabstrip();

          if (tab.getAttribute("selected") == "true") {
            this._fillTrailingGap();
            this._handleTabSelect();
          } else {
            this._notifyBackgroundTab(tab);
          }

          // 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.mTabstrip._updateScrollButtonsDisabledState();
        ]]></body>
      </method>

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

      <method name="_handleTabTelemetryStart">
        <parameter name="aTab"/>
        <parameter name="aURI"/>
        <body>
        <![CDATA[
          // Animation-smoothness telemetry/logging
          if (Services.telemetry.canRecord || this._tabAnimationLoggingEnabled) {
            if (aURI == "about:newtab" && (aTab._tPos == 1 || aTab._tPos == 2)) {
              // Indicate newtab page animation where other tabs are unaffected
              // (for which case, the 2nd or 3rd tabs are good representatives, even if not absolute)
              aTab._recordingTabOpenPlain = true;
            }
            aTab._recordingHandle = window.QueryInterface(Ci.nsIInterfaceRequestor)
                                          .getInterface(Ci.nsIDOMWindowUtils)
                                          .startFrameTimeRecording();
          }

          // Overall animation duration
          aTab._animStartTime = Date.now();
        ]]>
        </body>
      </method>

      <method name="_handleTabTelemetryEnd">
        <parameter name="aTab"/>
        <body>
        <![CDATA[
          if (!aTab._animStartTime) {
            return;
          }

          Services.telemetry.getHistogramById(aTab.closing ?
                                              "FX_TAB_ANIM_CLOSE_MS" :
                                              "FX_TAB_ANIM_OPEN_MS")
                            .add(Date.now() - aTab._animStartTime);
          aTab._animStartTime = 0;

          // Handle tab animation smoothness telemetry/logging of frame intervals and paint times
          if (!("_recordingHandle" in aTab)) {
            return;
          }

          let paints = {};
          let intervals = window.QueryInterface(Ci.nsIInterfaceRequestor)
                                .getInterface(Ci.nsIDOMWindowUtils)
                                .stopFrameTimeRecording(aTab._recordingHandle, paints);
          delete aTab._recordingHandle;
          paints = paints.value; // The result array itself.
          let frameCount = intervals.length;

          if (this._tabAnimationLoggingEnabled) {
            let msg = "Tab " + (aTab.closing ? "close" : "open") + " (Frame-interval / paint-processing):\n";
            for (let i = 0; i < frameCount; i++) {
              msg += Math.round(intervals[i]) + " / " + Math.round(paints[i]) + "\n";
            }
            Services.console.logStringMessage(msg);
          }

          // For telemetry, the first frame interval is not useful since it may represent an interval
          // to a relatively old frame (prior to recording start). So we'll ignore it for the average.
          // But if we recorded only 1 frame (very rare), then the first paint duration is a good
          // representative of the first frame interval for our cause (indicates very bad animation).
          // First paint duration is always useful for us.
          if (frameCount > 0) {
            let averageInterval = 0;
            let averagePaint = paints[0];
            for (let i = 1; i < frameCount; i++) {
              averageInterval += intervals[i];
              averagePaint    += paints[i];
            };
            averagePaint /= frameCount;
            averageInterval = (frameCount == 1)
                              ? averagePaint
                              : averageInterval / (frameCount - 1);

            Services.telemetry.getHistogramById("FX_TAB_ANIM_ANY_FRAME_INTERVAL_MS").add(averageInterval);
            Services.telemetry.getHistogramById("FX_TAB_ANIM_ANY_FRAME_PAINT_MS").add(averagePaint);

            if (aTab._recordingTabOpenPlain) {
              delete aTab._recordingTabOpenPlain;
              // While we do have a telemetry probe NEWTAB_PAGE_ENABLED to monitor newtab preview, it'll be
              // easier to overview the data without slicing by it. Hence the additional histograms with _PREVIEW.
              let preview = this._browserNewtabpageEnabled ? "_PREVIEW" : "";
              Services.telemetry.getHistogramById("FX_TAB_ANIM_OPEN" + preview + "_FRAME_INTERVAL_MS").add(averageInterval);
              Services.telemetry.getHistogramById("FX_TAB_ANIM_OPEN" + preview + "_FRAME_PAINT_MS").add(averagePaint);
            }
          }
        ]]>
        </body>
      </method>

      <!-- Deprecated stuff, implemented for backwards compatibility. -->
      <property name="mTabstripClosebutton" readonly="true"
                onget="return document.getElementById('tabs-closebutton');"/>
      <property name="mAllTabsPopup" readonly="true"
                onget="return document.getElementById('alltabs-popup');"/>
    </implementation>

    <handlers>
      <handler event="TabSelect" action="this._handleTabSelect();"/>

      <handler event="transitionend"><![CDATA[
        if (event.propertyName != "max-width")
          return;

        var tab = event.target;

        this._handleTabTelemetryEnd(tab);

        if (tab.getAttribute("fadein") == "true") {
          if (tab._fullyOpen)
            this.adjustTabstrip();
          else
            this._handleNewTab(tab);
        } else if (tab.closing) {
          this.tabbrowser._endRemoveTab(tab);
        }
      ]]></handler>

      <handler event="dblclick"><![CDATA[
        // When the tabbar has an unified appearance with the titlebar
        // and menubar, a double-click in it should have the same behavior
        // as double-clicking the titlebar
        if (TabsInTitlebar.enabled ||
            (TabsOnTop.enabled && this.parentNode._dragBindingAlive))
          return;

        if (event.button != 0 ||
            event.originalTarget.localName != "box")
          return;

        // See hack note in the tabbrowser-close-tab-button binding
        if (!this._blockDblClick)
          BrowserOpenTab();

        event.preventDefault();
      ]]></handler>

      <handler event="click"><![CDATA[
        if (event.button != 1)
          return;

        if (event.target.localName == "tab") {
          if (this.childNodes.length > 1 || !this._closeWindowWithLastTab)
            this.tabbrowser.removeTab(event.target, {animate: true, byMouse: true});
        } else if (event.originalTarget.localName == "box") {
          BrowserOpenTab();
        } else {
          return;
        }

        event.stopPropagation();
      ]]></handler>

      <handler event="keypress"><![CDATA[
        if (event.altKey || event.shiftKey ||
            !event.ctrlKey || event.metaKey)
          return;

        switch (event.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(event);
            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;
        }
        event.stopPropagation();
        event.preventDefault();
      ]]></handler>

      <handler event="dragstart"><![CDATA[
        var tab = this._getDragTargetTab(event);
        if (!tab)
          return;

        let dt = event.dataTransfer;
        dt.mozSetDataAt(TAB_DROP_TYPE, tab, 0);
        let browser = tab.linkedBrowser;

        // 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", browser.currentURI.spec, 0);

        // Set the cursor to an arrow during tab drags.
        dt.mozCursor = "default";

        // Create a canvas to which we capture the current tab.
        // Until canvas is HiDPI-aware (bug 780362), we need to scale the desired
        // canvas size (in CSS pixels) to the window's backing resolution in order
        // to get a full-resolution drag image for use on HiDPI displays.
        let windowUtils = window.getInterface(Ci.nsIDOMWindowUtils);
        let scale = windowUtils.screenPixelsPerCSSPixel / windowUtils.fullZoom;
        let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
        canvas.mozOpaque = true;
        canvas.width = 160 * scale;
        canvas.height = 90 * scale;
        PageThumbs.captureToCanvas(browser.contentWindow, canvas);
        dt.setDragImage(canvas, -16 * scale, -16 * scale);

        // _dragData.offsetX/Y give the coordinates that the mouse should be
        // positioned relative to the corner of the new window created upon
        // dragend such that the mouse appears to have the same position
        // relative to the corner of the dragged tab.
        function clientX(ele) ele.getBoundingClientRect().left;
        let tabOffsetX = clientX(tab) - clientX(this);
        tab._dragData = {
          offsetX: event.screenX - window.screenX - tabOffsetX,
          offsetY: event.screenY - window.screenY,
          scrollX: this.mTabstrip.scrollPosition,
          screenX: event.screenX
        };

        event.stopPropagation();
      ]]></handler>

      <handler event="dragover"><![CDATA[
        var effects = this._setEffectAllowedForDataTransfer(event);

        var ind = this._tabDropIndicator;
        if (effects == "" || effects == "none") {
          ind.collapsed = true;
          return;
        }
        event.preventDefault();
        event.stopPropagation();

        var tabStrip = this.mTabstrip;
        var ltr = (window.getComputedStyle(this, 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.getAttribute("overflow") == "true") {
          var targetAnonid = event.originalTarget.getAttribute("anonid");
          switch (targetAnonid) {
            case "scrollbutton-up":
              pixelsToScroll = tabStrip.scrollIncrement * -1;
              break;
            case "scrollbutton-down":
              pixelsToScroll = tabStrip.scrollIncrement;
              break;
          }
          if (pixelsToScroll)
            tabStrip.scrollByPixels((ltr ? 1 : -1) * pixelsToScroll);
        }

        if (effects == "move" &&
            this == event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0).parentNode) {
          ind.collapsed = true;
          this._animateTabMove(event);
          return;
        }

        this._finishAnimateTabMove();

        if (effects == "link") {
          let tab = this._getDragTargetTab(event);
          if (tab) {
            if (!this._dragTime)
              this._dragTime = Date.now();
            if (Date.now() >= this._dragTime + this._dragOverDelay)
              this.selectedItem = tab;
            ind.collapsed = true;
            return;
          }
        }

        var rect = tabStrip.getBoundingClientRect();
        var newMargin;
        if (pixelsToScroll) {
          // if we are scrolling, put the drop indicator at the edge
          // so that it doesn't jump while scrolling
          let scrollRect = tabStrip.scrollClientRect;
          let minMargin = scrollRect.left - rect.left;
          let maxMargin = Math.min(minMargin + scrollRect.width,
                                   scrollRect.right);
          if (!ltr)
            [minMargin, maxMargin] = [this.clientWidth - maxMargin,
                                      this.clientWidth - minMargin];
          newMargin = (pixelsToScroll > 0) ? maxMargin : minMargin;
        }
        else {
          let newIndex = this._getDropIndex(event);
          if (newIndex == this.childNodes.length) {
            let tabRect = this.childNodes[newIndex-1].getBoundingClientRect();
            if (ltr)
              newMargin = tabRect.right - rect.left;
            else
              newMargin = rect.right - tabRect.left;
          }
          else {
            let tabRect = this.childNodes[newIndex].getBoundingClientRect();
            if (ltr)
              newMargin = tabRect.left - rect.left;
            else
              newMargin = rect.right - tabRect.right;
          }
        }

        ind.collapsed = false;

        newMargin += ind.clientWidth / 2;
        if (!ltr)
          newMargin *= -1;

        ind.style.transform = "translate(" + Math.round(newMargin) + "px)";
        ind.style.MozMarginStart = (-ind.clientWidth) + "px";
      ]]></handler>

      <handler event="drop"><![CDATA[
        var dt = event.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._tabDropIndicator.collapsed = true;
        event.stopPropagation();
        if (draggedTab && dropEffect == "copy") {
          // copy the dropped tab (wherever it's from)
          let newIndex = this._getDropIndex(event);
          let newTab = this.tabbrowser.duplicateTab(draggedTab);
          this.tabbrowser.moveTabTo(newTab, newIndex);
          if (draggedTab.parentNode != this || event.shiftKey)
            this.selectedItem = newTab;
        } else if (draggedTab && draggedTab.parentNode == this) {
          this._finishAnimateTabMove();

          // actually move the dragged tab
          if ("animDropIndex" in draggedTab._dragData) {
            let newIndex = draggedTab._dragData.animDropIndex;
            if (newIndex > draggedTab._tPos)
              newIndex--;
            this.tabbrowser.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)
          let newIndex = this._getDropIndex(event);
          let newTab = this.tabbrowser.addTab("about:blank");
          let newBrowser = this.tabbrowser.getBrowserForTab(newTab);
          // Stop the about:blank load
          newBrowser.stop();
          // make sure it has a docshell
          newBrowser.docShell;

          let numPinned = this.tabbrowser._numPinnedTabs;
          if (newIndex < numPinned || draggedTab.pinned && newIndex == numPinned)
            this.tabbrowser.pinTab(newTab);
          this.tabbrowser.moveTabTo(newTab, newIndex);

          // We need to select the tab before calling swapBrowsersAndCloseOther
          // so that window.content in chrome windows points to the right tab
          // when pagehide/show events are fired.
          this.tabbrowser.selectedTab = newTab;

          draggedTab.parentNode._finishAnimateTabMove();
          this.tabbrowser.swapBrowsersAndCloseOther(newTab, draggedTab);

          // Call updateCurrentBrowser to make sure the URL bar is up to date
          // for our new tab after we've done swapBrowsersAndCloseOther.
          this.tabbrowser.updateCurrentBrowser(true);
        } else {
          // Pass true to disallow dropping javascript: or data: urls
          let url;
          try {
            url = browserDragAndDrop.drop(event, { }, true);
          } catch (ex) {}

          if (!url)
            return;

          let bgLoad = Services.prefs.getBoolPref("browser.tabs.loadInBackground");

          if (event.shiftKey)
            bgLoad = !bgLoad;

          let tab = this._getDragTargetTab(event);
          if (!tab || dropEffect == "copy") {
            // We're adding a new tab.
            let newIndex = this._getDropIndex(event);
            let newTab = this.tabbrowser.loadOneTab(url, {inBackground: bgLoad, allowThirdPartyFixup: true});
            this.tabbrowser.moveTabTo(newTab, newIndex);
          } else {
            // Load in an existing tab.
            try {
              this.tabbrowser.getBrowserForTab(tab).loadURIWithFlags(url, Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP);
              if (!bgLoad)
                this.selectedItem = tab;
            } catch(ex) {
              // Just ignore invalid urls
            }
          }
        }

        if (draggedTab) {
          delete draggedTab._dragData;
        }
      ]]></handler>

      <handler event="dragend"><![CDATA[
        // Note: while this case is correctly handled here, this event
        // isn't dispatched when the tab is moved within the tabstrip,
        // see bug 460801.

        this._finishAnimateTabMove();

        var dt = event.dataTransfer;
        var draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
        if (dt.mozUserCancelled || dt.dropEffect != "none") {
          delete draggedTab._dragData;
          return;
        }

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

        // screen.availLeft et. al. only check the screen that this window is on,
        // but we want to look at the screen the tab is being dropped onto.
        var sX = {}, sY = {}, sWidth = {}, sHeight = {};
        Cc["@mozilla.org/gfx/screenmanager;1"]
          .getService(Ci.nsIScreenManager)
          .screenForRect(eX, eY, 1, 1)
          .GetAvailRect(sX, sY, sWidth, sHeight);
        // ensure new window entirely within screen
        var winWidth = Math.min(window.outerWidth, sWidth.value);
        var winHeight = Math.min(window.outerHeight, sHeight.value);
        var left = Math.min(Math.max(eX - draggedTab._dragData.offsetX, sX.value),
                            sX.value + sWidth.value - winWidth);
        var top = Math.min(Math.max(eY - draggedTab._dragData.offsetY, sY.value),
                           sY.value + sHeight.value - winHeight);

        delete draggedTab._dragData;

        if (this.tabbrowser.tabs.length == 1) {
          // resize _before_ move to ensure the window fits the new screen.  if
          // the window is too large for its screen, the window manager may do
          // automatic repositioning.
          window.resizeTo(winWidth, winHeight);
          window.moveTo(left, top);
          window.focus();
        } else {
          this.tabbrowser.replaceTabWithWindow(draggedTab, { screenX: left,
                                                             screenY: top,
                                                             });
        }
        event.stopPropagation();
      ]]></handler>

      <handler event="dragexit"><![CDATA[
        this._dragTime = 0;

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

        this._tabDropIndicator.collapsed = true;
        event.stopPropagation();
      ]]></handler>
    </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>).
  -->
  <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);
        var tabContainer = bindingParent.parentNode;
        /* 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;

        tabContainer.tabbrowser.removeTab(bindingParent, {animate: true, byMouse: true});
        tabContainer._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 dblclick handler).
         */
        var clickedOnce = false;
        function enableDblClick(event) {
          var target = event.originalTarget;
          if (target.className == 'tab-close-button')
            target._ignoredClick = true;
          if (!clickedOnce) {
            clickedOnce = true;
            return;
          }
          tabContainer._blockDblClick = false;
          tabContainer.removeEventListener("click", enableDblClick, true);
        }
        tabContainer.addEventListener("click", enableDblClick, true);
      ]]></handler>

      <handler event="dblclick" button="0" phase="capturing">
        // for the one-close-button case
        event.stopPropagation();
      </handler>

      <handler event="dragstart">
        event.stopPropagation();
      </handler>
    </handlers>
  </binding>

  <binding id="tabbrowser-tab" display="xul:hbox"
           extends="chrome://global/content/bindings/tabbox.xml#tab">
    <resources>
      <stylesheet src="chrome://browser/content/tabbrowser.css"/>
    </resources>

    <content context="tabContextMenu" closetabtext="&closeTab.label;">
      <xul:stack class="tab-stack" flex="1">
        <xul:hbox xbl:inherits="pinned,selected,titlechanged"
                  class="tab-background">
          <xul:hbox xbl:inherits="pinned,selected,titlechanged"
                    class="tab-background-start"/>
          <xul:hbox xbl:inherits="pinned,selected,titlechanged"
                    class="tab-background-middle"/>
          <xul:hbox xbl:inherits="pinned,selected,titlechanged"
                    class="tab-background-end"/>
        </xul:hbox>
        <xul:hbox xbl:inherits="pinned,selected,titlechanged"
                  class="tab-content" align="center">
          <xul:image xbl:inherits="fadein,pinned,busy,progress,selected"
                     class="tab-throbber"
                     role="presentation"
                     layer="true" />
          <xul:image xbl:inherits="src=image,fadein,pinned,selected"
                     class="tab-icon-image"
                     validate="never"
                     role="presentation"/>
          <xul:label flex="1"
                     xbl:inherits="value=label,crop,accesskey,fadein,pinned,selected"
                     class="tab-text tab-label"
                     role="presentation"/>
          <xul:toolbarbutton anonid="close-button"
                             xbl:inherits="fadein,pinned,selected"
                             class="tab-close-button"/>
        </xul:hbox>
      </xul:stack>
    </content>

    <implementation>
      <property name="pinned" readonly="true">
        <getter>
          return this.getAttribute("pinned") == "true";
        </getter>
      </property>
      <property name="hidden" readonly="true">
        <getter>
          return this.getAttribute("hidden") == "true";
        </getter>
      </property>

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

    <handlers>
      <handler event="mouseover"><![CDATA[
        let anonid = event.originalTarget.getAttribute("anonid");
        if (anonid == "close-button")
          this.mOverCloseButton = true;

        let tab = event.target;
        if (tab.closing)
          return;

        let tabContainer = this.parentNode;
        let visibleTabs = tabContainer.tabbrowser.visibleTabs;
        let tabIndex = visibleTabs.indexOf(tab);
        if (tabIndex == 0) {
          tabContainer._beforeHoveredTab = null;
        } else {
          let candidate = visibleTabs[tabIndex - 1];
          if (!candidate.selected) {
            tabContainer._beforeHoveredTab = candidate;
            candidate.setAttribute("beforehovered", "true");
          }
        }

        if (tabIndex == visibleTabs.length - 1) {
          tabContainer._afterHoveredTab = null;
        } else {
          let candidate = visibleTabs[tabIndex + 1];
          if (!candidate.selected) {
            tabContainer._afterHoveredTab = candidate;
            candidate.setAttribute("afterhovered", "true");
          }
        }
      ]]></handler>
      <handler event="mouseout"><![CDATA[
        let anonid = event.originalTarget.getAttribute("anonid");
        if (anonid == "close-button")
          this.mOverCloseButton = false;

        let tabContainer = this.parentNode;
        if (tabContainer._beforeHoveredTab) {
          tabContainer._beforeHoveredTab.removeAttribute("beforehovered");
          tabContainer._beforeHoveredTab = null;
        }
        if (tabContainer._afterHoveredTab) {
          tabContainer._afterHoveredTab.removeAttribute("afterhovered");
          tabContainer._afterHoveredTab = null;
        }
      ]]></handler>
      <handler event="dragstart" phase="capturing">
        this.style.MozUserFocus = '';
      </handler>
      <handler event="mousedown" phase="capturing">
      <![CDATA[
        if (this.selected) {
          this.style.MozUserFocus = 'ignore';
          this.clientTop; // just using this to flush style updates
        } else if (this.mOverCloseButton) {
          // Prevent tabbox.xml from selecting the tab.
          event.stopPropagation();
        }
      ]]>
      </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="_tabOnAttrModified">
        <parameter name="aEvent"/>
        <body><![CDATA[
          var tab = aEvent.target;
          if (tab.mCorrespondingMenuitem)
            this._setMenuitemAttributes(tab.mCorrespondingMenuitem, tab);
        ]]></body>
      </method>

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

      <method name="handleEvent">
        <parameter name="aEvent"/>
        <body><![CDATA[
          switch (aEvent.type) {
            case "TabAttrModified":
              this._tabOnAttrModified(aEvent);
              break;
            case "TabClose":
              this._tabOnTabClose(aEvent);
              break;
            case "scroll":
              this._updateTabsVisibilityStatus();
              break;
          }
        ]]></body>
      </method>

      <method name="_updateTabsVisibilityStatus">
        <body><![CDATA[
          var tabContainer = gBrowser.tabContainer;
          // 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++) {
            let curTab = this.childNodes[i].tab;
            if (!curTab) // "Tab Groups" menuitem and its menuseparator
              continue;
            let curTabBO = curTab.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 menuitem-with-favicon");

          this._setMenuitemAttributes(menuItem, aTab);

          aTab.mCorrespondingMenuitem = menuItem;
          menuItem.tab = aTab;

          this.appendChild(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"));

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

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

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

    <handlers>
      <handler event="popupshowing">
      <![CDATA[
        var tabcontainer = gBrowser.tabContainer;

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

        let tabs = gBrowser.visibleTabs;
        for (var i = 0; i < tabs.length; i++) {
          if (!tabs[i].pinned)
            this._createTabMenuItem(tabs[i]);
        }
        this._updateTabsVisibilityStatus();
      ]]></handler>

      <handler event="popuphidden">
      <![CDATA[
        // clear out the menu popup and remove the listeners
        for (let i = this.childNodes.length - 1; i > 0; i--) {
          let menuItem = this.childNodes[i];
          if (menuItem.tab) {
            menuItem.tab.mCorrespondingMenuitem = null;
            this.removeChild(menuItem);
          }
        }
        var tabcontainer = gBrowser.tabContainer;
        tabcontainer.mTabstrip.removeEventListener("scroll", this, false);
        tabcontainer.removeEventListener("TabAttrModified", this, false);
        tabcontainer.removeEventListener("TabClose", this, false);
      ]]></handler>

      <handler event="DOMMenuItemActive">
      <![CDATA[
        var tab = event.target.tab;
        if (tab) {
          let overLink = tab.linkedBrowser.currentURI.spec;
          if (overLink == "about:blank")
            overLink = "";
          XULBrowserWindow.setOverLink(overLink, null);
        }
      ]]></handler>

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

      <handler event="command"><![CDATA[
        if (event.target.tab)
          gBrowser.selectedTab = event.target.tab;
      ]]></handler>

    </handlers>
  </binding>

  <binding id="statuspanel" display="xul:hbox">
    <content>
      <xul:hbox class="statuspanel-inner">
        <xul:label class="statuspanel-label"
                   role="status"
                   aria-live="off"
                   xbl:inherits="value=label,crop,mirror"
                   flex="1"
                   crop="end"/>
      </xul:hbox>
    </content>

    <implementation implements="nsIDOMEventListener">
      <constructor><![CDATA[
        window.addEventListener("resize", this, false);
      ]]></constructor>

      <destructor><![CDATA[
        window.removeEventListener("resize", this, false);
        MousePosTracker.removeListener(this);
      ]]></destructor>

      <property name="label">
        <setter><![CDATA[
          if (!this.label) {
            this.removeAttribute("mirror");
            this.removeAttribute("sizelimit");
          }

          this.style.minWidth = this.getAttribute("type") == "status" &&
                                this.getAttribute("previoustype") == "status"
                                  ? getComputedStyle(this).width : "";

          if (val) {
            this.setAttribute("label", val);
            this.removeAttribute("inactive");
            this._calcMouseTargetRect();
            MousePosTracker.addListener(this);
          } else {
            this.setAttribute("inactive", "true");
            MousePosTracker.removeListener(this);
          }

          return val;
        ]]></setter>
        <getter>
          return this.hasAttribute("inactive") ? "" : this.getAttribute("label");
        </getter>
      </property>

      <method name="getMouseTargetRect">
        <body><![CDATA[
          return this._mouseTargetRect;
        ]]></body>
      </method>

      <method name="onMouseEnter">
        <body>
          this._mirror();
        </body>
      </method>

      <method name="onMouseLeave">
        <body>
          this._mirror();
        </body>
      </method>

      <method name="handleEvent">
        <parameter name="event"/>
        <body><![CDATA[
          if (!this.label)
            return;

          switch (event.type) {
            case "resize":
              this._calcMouseTargetRect();
              break;
          }
        ]]></body>
      </method>

      <method name="_calcMouseTargetRect">
        <body><![CDATA[
          let alignRight = false;

          if (getComputedStyle(document.documentElement).direction == "rtl")
            alignRight = !alignRight;

          let rect = this.getBoundingClientRect();
          this._mouseTargetRect = {
            top:    rect.top,
            bottom: rect.bottom,
            left:   alignRight ? window.innerWidth - rect.width : 0,
            right:  alignRight ? window.innerWidth : rect.width
          };
        ]]></body>
      </method>

      <method name="_mirror">
        <body>
          if (this.hasAttribute("mirror"))
            this.removeAttribute("mirror");
          else
            this.setAttribute("mirror", "true");

          if (!this.hasAttribute("sizelimit")) {
            this.setAttribute("sizelimit", "true");
            this._calcMouseTargetRect();
          }
        </body>
      </method>
    </implementation>
  </binding>

</bindings>
