browser/base/content/tabbrowser.xml

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 <?xml version="1.0"?>
     3 <!-- This Source Code Form is subject to the terms of the Mozilla Public
     4    - License, v. 2.0. If a copy of the MPL was not distributed with this
     5    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
     7 <!DOCTYPE bindings [
     8 <!ENTITY % tabBrowserDTD SYSTEM "chrome://browser/locale/tabbrowser.dtd" >
     9 %tabBrowserDTD;
    10 ]>
    12 # MAKE_E10S_WORK surrounds code needed to have the front-end try to be smart
    13 # about using non-remote browsers for loading certain URIs when remote tabs
    14 # (browser.tabs.remote) are enabled.
    15 #define MAKE_E10S_WORK 1
    17 <bindings id="tabBrowserBindings"
    18           xmlns="http://www.mozilla.org/xbl"
    19           xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
    20           xmlns:xbl="http://www.mozilla.org/xbl">
    22   <binding id="tabbrowser">
    23     <resources>
    24       <stylesheet src="chrome://browser/content/tabbrowser.css"/>
    25     </resources>
    27     <content>
    28       <xul:stringbundle anonid="tbstringbundle" src="chrome://browser/locale/tabbrowser.properties"/>
    29       <xul:tabbox anonid="tabbox" class="tabbrowser-tabbox"
    30                   flex="1" eventnode="document" xbl:inherits="handleCtrlPageUpDown"
    31                   onselect="if (event.target.localName == 'tabpanels') this.parentNode.updateCurrentBrowser();">
    32         <xul:tabpanels flex="1" class="plain" selectedIndex="0" anonid="panelcontainer">
    33           <xul:notificationbox flex="1">
    34             <xul:hbox flex="1" class="browserSidebarContainer">
    35               <xul:vbox flex="1" class="browserContainer">
    36                 <xul:stack flex="1" class="browserStack" anonid="browserStack">
    37                   <xul:browser anonid="initialBrowser" type="content-primary" message="true"
    38                                xbl:inherits="tooltip=contenttooltip,contextmenu=contentcontextmenu,autocompletepopup,selectpopup"/>
    39                 </xul:stack>
    40               </xul:vbox>
    41             </xul:hbox>
    42           </xul:notificationbox>
    43         </xul:tabpanels>
    44       </xul:tabbox>
    45       <children/>
    46     </content>
    47     <implementation implements="nsIDOMEventListener, nsIMessageListener">
    49       <property name="tabContextMenu" readonly="true"
    50                 onget="return this.tabContainer.contextMenu;"/>
    52       <field name="tabContainer" readonly="true">
    53         document.getElementById(this.getAttribute("tabcontainer"));
    54       </field>
    55       <field name="tabs" readonly="true">
    56         this.tabContainer.childNodes;
    57       </field>
    59       <property name="visibleTabs" readonly="true">
    60         <getter><![CDATA[
    61           if (!this._visibleTabs)
    62             this._visibleTabs = Array.filter(this.tabs,
    63                                              function (tab) !tab.hidden && !tab.closing);
    64           return this._visibleTabs;
    65         ]]></getter>
    66       </property>
    68       <field name="closingTabsEnum" readonly="true">({ ALL: 0, OTHER: 1, TO_END: 2 });</field>
    70       <field name="_visibleTabs">null</field>
    72       <field name="mURIFixup" readonly="true">
    73         Components.classes["@mozilla.org/docshell/urifixup;1"]
    74                   .getService(Components.interfaces.nsIURIFixup);
    75       </field>
    76       <field name="mFaviconService" readonly="true">
    77         Components.classes["@mozilla.org/browser/favicon-service;1"]
    78                   .getService(Components.interfaces.nsIFaviconService);
    79       </field>
    80       <field name="_placesAutocomplete" readonly="true">
    81          Components.classes["@mozilla.org/autocomplete/search;1?name=history"]
    82                    .getService(Components.interfaces.mozIPlacesAutoComplete);
    83       </field>
    84       <field name="_unifiedComplete" readonly="true">
    85          Components.classes["@mozilla.org/autocomplete/search;1?name=unifiedcomplete"]
    86                    .getService(Components.interfaces.mozIPlacesAutoComplete);
    87       </field>
    88       <field name="mTabBox" readonly="true">
    89         document.getAnonymousElementByAttribute(this, "anonid", "tabbox");
    90       </field>
    91       <field name="mPanelContainer" readonly="true">
    92         document.getAnonymousElementByAttribute(this, "anonid", "panelcontainer");
    93       </field>
    94       <field name="mStringBundle">
    95         document.getAnonymousElementByAttribute(this, "anonid", "tbstringbundle");
    96       </field>
    97       <field name="mCurrentTab">
    98         null
    99       </field>
   100       <field name="_lastRelatedTab">
   101         null
   102       </field>
   103       <field name="mCurrentBrowser">
   104         null
   105       </field>
   106       <field name="mProgressListeners">
   107         []
   108       </field>
   109       <field name="mTabsProgressListeners">
   110         []
   111       </field>
   112       <field name="mTabListeners">
   113         []
   114       </field>
   115       <field name="mTabFilters">
   116         []
   117       </field>
   118       <field name="mIsBusy">
   119         false
   120       </field>
   121       <field name="arrowKeysShouldWrap" readonly="true">
   122 #ifdef XP_MACOSX
   123         true
   124 #else
   125         false
   126 #endif
   127       </field>
   129       <field name="_autoScrollPopup">
   130         null
   131       </field>
   133       <field name="_previewMode">
   134         false
   135       </field>
   137       <field name="_lastFindValue">
   138         ""
   139       </field>
   141       <property name="_numPinnedTabs" readonly="true">
   142         <getter><![CDATA[
   143           for (var i = 0; i < this.tabs.length; i++) {
   144             if (!this.tabs[i].pinned)
   145               break;
   146           }
   147           return i;
   148         ]]></getter>
   149       </property>
   151       <method name="isFindBarInitialized">
   152         <parameter name="aTab"/>
   153         <body><![CDATA[
   154           return (aTab || this.selectedTab)._findBar != undefined;
   155         ]]></body>
   156       </method>
   158       <method name="getFindBar">
   159         <parameter name="aTab"/>
   160         <body><![CDATA[
   161           if (!aTab)
   162             aTab = this.selectedTab;
   164           if (aTab._findBar)
   165             return aTab._findBar;
   167           let findBar = document.createElementNS(this.namespaceURI, "findbar");
   168           let browser = this.getBrowserForTab(aTab);
   169           let browserContainer = this.getBrowserContainer(browser);
   170           browserContainer.appendChild(findBar);
   172           // Force a style flush to ensure that our binding is attached.
   173           findBar.clientTop;
   175           findBar.browser = browser;
   176           findBar._findField.value = this._lastFindValue;
   178           aTab._findBar = findBar;
   180           let event = document.createEvent("Events");
   181           event.initEvent("TabFindInitialized", true, false);
   182           aTab.dispatchEvent(event);
   184           return findBar;
   185         ]]></body>
   186       </method>
   188       <method name="getStatusPanel">
   189         <body><![CDATA[
   190           if (!this._statusPanel) {
   191             this._statusPanel = document.createElementNS(this.namespaceURI, "statuspanel");
   192             this._statusPanel.setAttribute("inactive", "true");
   193             this._statusPanel.setAttribute("layer", "true");
   194             this._appendStatusPanel();
   195           }
   196           return this._statusPanel;
   197         ]]></body>
   198       </method>
   200       <method name="_appendStatusPanel">
   201         <body><![CDATA[
   202           if (this._statusPanel) {
   203             let browser = this.selectedBrowser;
   204             let browserContainer = this.getBrowserContainer(browser);
   205             browserContainer.insertBefore(this._statusPanel, browser.parentNode.nextSibling);
   206           }
   207         ]]></body>
   208       </method>
   210       <method name="updateWindowResizers">
   211         <body><![CDATA[
   212           if (!window.gShowPageResizers)
   213             return;
   215           var show = window.windowState == window.STATE_NORMAL;
   216           for (let i = 0; i < this.browsers.length; i++) {
   217             this.browsers[i].showWindowResizer = show;
   218           }
   219         ]]></body>
   220       </method>
   222       <method name="_setCloseKeyState">
   223         <parameter name="aEnabled"/>
   224         <body><![CDATA[
   225           let keyClose = document.getElementById("key_close");
   226           let closeKeyEnabled = keyClose.getAttribute("disabled") != "true";
   227           if (closeKeyEnabled == aEnabled)
   228             return;
   230           if (aEnabled)
   231             keyClose.removeAttribute("disabled");
   232           else
   233             keyClose.setAttribute("disabled", "true");
   235           // We also want to remove the keyboard shortcut from the file menu
   236           // when the shortcut is disabled, and bring it back when it's
   237           // renabled.
   238           //
   239           // Fixing bug 630826 could make that happen automatically.
   240           // Fixing bug 630830 could avoid the ugly hack below.
   242           let closeMenuItem = document.getElementById("menu_close");
   243           let parentPopup = closeMenuItem.parentNode;
   244           let nextItem = closeMenuItem.nextSibling;
   245           let clonedItem = closeMenuItem.cloneNode(true);
   247           parentPopup.removeChild(closeMenuItem);
   249           if (aEnabled)
   250             clonedItem.setAttribute("key", "key_close");
   251           else
   252             clonedItem.removeAttribute("key");
   254           parentPopup.insertBefore(clonedItem, nextItem);
   255         ]]></body>
   256       </method>
   258       <method name="pinTab">
   259         <parameter name="aTab"/>
   260         <body><![CDATA[
   261           if (aTab.pinned)
   262             return;
   264           if (aTab.hidden)
   265             this.showTab(aTab);
   267           this.moveTabTo(aTab, this._numPinnedTabs);
   268           aTab.setAttribute("pinned", "true");
   269           this.tabContainer._unlockTabSizing();
   270           this.tabContainer._positionPinnedTabs();
   271           this.tabContainer.adjustTabstrip();
   273           // Bug 961867 - [e10s] Implement the logic for app tabs
   274           if (!gMultiProcessBrowser)
   275             this.getBrowserForTab(aTab).docShell.isAppTab = true;
   277           if (aTab.selected)
   278             this._setCloseKeyState(false);
   280           let event = document.createEvent("Events");
   281           event.initEvent("TabPinned", true, false);
   282           aTab.dispatchEvent(event);
   283         ]]></body>
   284       </method>
   286       <method name="unpinTab">
   287         <parameter name="aTab"/>
   288         <body><![CDATA[
   289           if (!aTab.pinned)
   290             return;
   292           this.moveTabTo(aTab, this._numPinnedTabs - 1);
   293           aTab.removeAttribute("pinned");
   294           aTab.style.MozMarginStart = "";
   295           this.tabContainer._unlockTabSizing();
   296           this.tabContainer._positionPinnedTabs();
   297           this.tabContainer.adjustTabstrip();
   299           // Bug 961867 - [e10s] Implement the logic for app tabs
   300           if (!gMultiProcessBrowser)
   301             this.getBrowserForTab(aTab).docShell.isAppTab = false;
   303           if (aTab.selected)
   304             this._setCloseKeyState(true);
   306           let event = document.createEvent("Events");
   307           event.initEvent("TabUnpinned", true, false);
   308           aTab.dispatchEvent(event);
   309         ]]></body>
   310       </method>
   312       <method name="previewTab">
   313         <parameter name="aTab"/>
   314         <parameter name="aCallback"/>
   315         <body>
   316           <![CDATA[
   317             let currentTab = this.selectedTab;
   318             try {
   319               // Suppress focus, ownership and selected tab changes
   320               this._previewMode = true;
   321               this.selectedTab = aTab;
   322               aCallback();
   323             } finally {
   324               this.selectedTab = currentTab;
   325               this._previewMode = false;
   326             }
   327           ]]>
   328         </body>
   329       </method>
   331       <method name="getBrowserAtIndex">
   332         <parameter name="aIndex"/>
   333         <body>
   334           <![CDATA[
   335             return this.browsers[aIndex];
   336           ]]>
   337         </body>
   338       </method>
   340       <method name="getBrowserIndexForDocument">
   341         <parameter name="aDocument"/>
   342         <body>
   343           <![CDATA[
   344             var tab = this._getTabForContentWindow(aDocument.defaultView);
   345             return tab ? tab._tPos : -1;
   346           ]]>
   347         </body>
   348       </method>
   350       <method name="getBrowserForDocument">
   351         <parameter name="aDocument"/>
   352         <body>
   353           <![CDATA[
   354             var tab = this._getTabForContentWindow(aDocument.defaultView);
   355             return tab ? tab.linkedBrowser : null;
   356           ]]>
   357         </body>
   358       </method>
   360       <method name="_getTabForContentWindow">
   361         <parameter name="aWindow"/>
   362         <body>
   363         <![CDATA[
   364           for (let i = 0; i < this.browsers.length; i++) {
   365             if (this.browsers[i].contentWindow == aWindow)
   366               return this.tabs[i];
   367           }
   368           return null;
   369         ]]>
   370         </body>
   371       </method>
   373       <method name="_getTabForBrowser">
   374         <parameter name="aBrowser"/>
   375         <body>
   376         <![CDATA[
   377           for (let i = 0; i < this.tabs.length; i++) {
   378             if (this.tabs[i].linkedBrowser == aBrowser)
   379               return this.tabs[i];
   380           }
   381           return null;
   382         ]]>
   383         </body>
   384       </method>
   386       <method name="getNotificationBox">
   387         <parameter name="aBrowser"/>
   388         <body>
   389           <![CDATA[
   390             return this.getSidebarContainer(aBrowser).parentNode;
   391           ]]>
   392         </body>
   393       </method>
   395       <method name="getSidebarContainer">
   396         <parameter name="aBrowser"/>
   397         <body>
   398           <![CDATA[
   399             return this.getBrowserContainer(aBrowser).parentNode;
   400           ]]>
   401         </body>
   402       </method>
   404       <method name="getBrowserContainer">
   405         <parameter name="aBrowser"/>
   406         <body>
   407           <![CDATA[
   408             return (aBrowser || this.mCurrentBrowser).parentNode.parentNode;
   409           ]]>
   410         </body>
   411       </method>
   413       <method name="getTabModalPromptBox">
   414         <parameter name="aBrowser"/>
   415         <body>
   416           <![CDATA[
   417             const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
   418             let browser = (aBrowser || this.mCurrentBrowser);
   419             let stack = browser.parentNode;
   420             let self = this;
   422             let promptBox = {
   423               appendPrompt : function(args, onCloseCallback) {
   424                 let newPrompt = document.createElementNS(XUL_NS, "tabmodalprompt");
   425                 stack.appendChild(newPrompt);
   426                 browser.setAttribute("tabmodalPromptShowing", true);
   428                 newPrompt.clientTop; // style flush to assure binding is attached
   430                 let tab = self._getTabForBrowser(browser);
   431                 newPrompt.init(args, tab, onCloseCallback);
   432                 return newPrompt;
   433               },
   435               removePrompt : function(aPrompt) {
   436                 stack.removeChild(aPrompt);
   438                 let prompts = this.listPrompts();
   439                 if (prompts.length) {
   440                   let prompt = prompts[prompts.length - 1];
   441                   prompt.Dialog.setDefaultFocus();
   442                 } else {
   443                   browser.removeAttribute("tabmodalPromptShowing");
   444                   browser.focus();
   445                 }
   446               },
   448               listPrompts : function(aPrompt) {
   449                 let els = stack.getElementsByTagNameNS(XUL_NS, "tabmodalprompt");
   450                 // NodeList --> real JS array
   451                 let prompts = Array.slice(els);
   452                 return prompts;
   453               },
   454             };
   456             return promptBox;
   457           ]]>
   458         </body>
   459       </method>
   461       <method name="_callProgressListeners">
   462         <parameter name="aBrowser"/>
   463         <parameter name="aMethod"/>
   464         <parameter name="aArguments"/>
   465         <parameter name="aCallGlobalListeners"/>
   466         <parameter name="aCallTabsListeners"/>
   467         <body><![CDATA[
   468           var rv = true;
   470           if (!aBrowser)
   471             aBrowser = this.mCurrentBrowser;
   473           if (aCallGlobalListeners != false &&
   474               aBrowser == this.mCurrentBrowser) {
   475             this.mProgressListeners.forEach(function (p) {
   476               if (aMethod in p) {
   477                 try {
   478                   if (!p[aMethod].apply(p, aArguments))
   479                     rv = false;
   480                 } catch (e) {
   481                   // don't inhibit other listeners
   482                   Components.utils.reportError(e);
   483                 }
   484               }
   485             });
   486           }
   488           if (aCallTabsListeners != false) {
   489             aArguments.unshift(aBrowser);
   491             this.mTabsProgressListeners.forEach(function (p) {
   492               if (aMethod in p) {
   493                 try {
   494                   if (!p[aMethod].apply(p, aArguments))
   495                     rv = false;
   496                 } catch (e) {
   497                   // don't inhibit other listeners
   498                   Components.utils.reportError(e);
   499                 }
   500               }
   501             });
   502           }
   504           return rv;
   505         ]]></body>
   506       </method>
   508       <!-- A web progress listener object definition for a given tab. -->
   509       <method name="mTabProgressListener">
   510         <parameter name="aTab"/>
   511         <parameter name="aBrowser"/>
   512         <parameter name="aStartsBlank"/>
   513         <body>
   514         <![CDATA[
   515           return ({
   516             mTabBrowser: this,
   517             mTab: aTab,
   518             mBrowser: aBrowser,
   519             mBlank: aStartsBlank,
   521             // cache flags for correct status UI update after tab switching
   522             mStateFlags: 0,
   523             mStatus: 0,
   524             mMessage: "",
   525             mTotalProgress: 0,
   527             // count of open requests (should always be 0 or 1)
   528             mRequestCount: 0,
   530             destroy: function () {
   531               delete this.mTab;
   532               delete this.mBrowser;
   533               delete this.mTabBrowser;
   534             },
   536             _callProgressListeners: function () {
   537               Array.unshift(arguments, this.mBrowser);
   538               return this.mTabBrowser._callProgressListeners.apply(this.mTabBrowser, arguments);
   539             },
   541             _shouldShowProgress: function (aRequest) {
   542               if (this.mBlank)
   543                 return false;
   545               if (gMultiProcessBrowser)
   546                 return true;
   548               // Don't show progress indicators in tabs for about: URIs
   549               // pointing to local resources.
   550               try {
   551                 let channel = aRequest.QueryInterface(Ci.nsIChannel);
   552                 if (channel.originalURI.schemeIs("about") &&
   553                     (channel.URI.schemeIs("jar") || channel.URI.schemeIs("file")))
   554                   return false;
   555               } catch (e) {}
   557               return true;
   558             },
   560             onProgressChange: function (aWebProgress, aRequest,
   561                                         aCurSelfProgress, aMaxSelfProgress,
   562                                         aCurTotalProgress, aMaxTotalProgress) {
   563               this.mTotalProgress = aMaxTotalProgress ? aCurTotalProgress / aMaxTotalProgress : 0;
   565               if (!this._shouldShowProgress(aRequest))
   566                 return;
   568               if (this.mTotalProgress)
   569                 this.mTab.setAttribute("progress", "true");
   571               this._callProgressListeners("onProgressChange",
   572                                           [aWebProgress, aRequest,
   573                                            aCurSelfProgress, aMaxSelfProgress,
   574                                            aCurTotalProgress, aMaxTotalProgress]);
   575             },
   577             onProgressChange64: function (aWebProgress, aRequest,
   578                                           aCurSelfProgress, aMaxSelfProgress,
   579                                           aCurTotalProgress, aMaxTotalProgress) {
   580               return this.onProgressChange(aWebProgress, aRequest,
   581                 aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress,
   582                 aMaxTotalProgress);
   583             },
   585             onStateChange: function (aWebProgress, aRequest, aStateFlags, aStatus) {
   586               if (!aRequest)
   587                 return;
   589               var oldBlank = this.mBlank;
   591               const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
   592               const nsIChannel = Components.interfaces.nsIChannel;
   594               if (aStateFlags & nsIWebProgressListener.STATE_START) {
   595                 this.mRequestCount++;
   596               }
   597               else if (aStateFlags & nsIWebProgressListener.STATE_STOP) {
   598                 const NS_ERROR_UNKNOWN_HOST = 2152398878;
   599                 if (--this.mRequestCount > 0 && aStatus == NS_ERROR_UNKNOWN_HOST) {
   600                   // to prevent bug 235825: wait for the request handled
   601                   // by the automatic keyword resolver
   602                   return;
   603                 }
   604                 // since we (try to) only handle STATE_STOP of the last request,
   605                 // the count of open requests should now be 0
   606                 this.mRequestCount = 0;
   607               }
   609               if (aStateFlags & nsIWebProgressListener.STATE_START &&
   610                   aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
   611                 // It's okay to clear what the user typed when we start
   612                 // loading a document. If the user types, this counter gets
   613                 // set to zero, if the document load ends without an
   614                 // onLocationChange, this counter gets decremented
   615                 // (so we keep it while switching tabs after failed loads)
   616                 // We need to add 2 because loadURIWithFlags may have
   617                 // cancelled a pending load which would have cleared
   618                 // its anchor scroll detection temporary increment.
   619                 if (aWebProgress.isTopLevel)
   620                   this.mBrowser.userTypedClear += 2;
   622                 if (this._shouldShowProgress(aRequest)) {
   623                   if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) {
   624                     this.mTab.setAttribute("busy", "true");
   625                     if (!(aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_RELOAD))
   626                       this.mTabBrowser.setTabTitleLoading(this.mTab);
   627                   }
   629                   if (this.mTab.selected)
   630                     this.mTabBrowser.mIsBusy = true;
   631                 }
   632               }
   633               else if (aStateFlags & nsIWebProgressListener.STATE_STOP &&
   634                        aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
   636                 if (this.mTab.hasAttribute("busy")) {
   637                   this.mTab.removeAttribute("busy");
   638                   this.mTabBrowser._tabAttrModified(this.mTab);
   639                   if (!this.mTab.selected)
   640                     this.mTab.setAttribute("unread", "true");
   641                 }
   642                 this.mTab.removeAttribute("progress");
   644                 if (aWebProgress.isTopLevel) {
   645                   if (!Components.isSuccessCode(aStatus) &&
   646                       !isTabEmpty(this.mTab)) {
   647                     // Restore the current document's location in case the
   648                     // request was stopped (possibly from a content script)
   649                     // before the location changed.
   651                     this.mBrowser.userTypedValue = null;
   653                     if (this.mTab.selected && gURLBar)
   654                       URLBarSetURI();
   655                   } else {
   656                     // The document is done loading, we no longer want the
   657                     // value cleared.
   659                     if (this.mBrowser.userTypedClear > 1)
   660                       this.mBrowser.userTypedClear -= 2;
   661                     else if (this.mBrowser.userTypedClear > 0)
   662                       this.mBrowser.userTypedClear--;
   663                   }
   665                   if (!this.mBrowser.mIconURL)
   666                     this.mTabBrowser.useDefaultIcon(this.mTab);
   667                 }
   669                 if (this.mBlank)
   670                   this.mBlank = false;
   672                 var location = aRequest.QueryInterface(nsIChannel).URI;
   674                 // For keyword URIs clear the user typed value since they will be changed into real URIs
   675                 if (location.scheme == "keyword")
   676                   this.mBrowser.userTypedValue = null;
   678                 if (this.mTab.label == this.mTabBrowser.mStringBundle.getString("tabs.connecting"))
   679                   this.mTabBrowser.setTabTitle(this.mTab);
   681                 if (this.mTab.selected)
   682                   this.mTabBrowser.mIsBusy = false;
   683               }
   685               if (oldBlank) {
   686                 this._callProgressListeners("onUpdateCurrentBrowser",
   687                                             [aStateFlags, aStatus, "", 0],
   688                                             true, false);
   689               } else {
   690                 this._callProgressListeners("onStateChange",
   691                                             [aWebProgress, aRequest, aStateFlags, aStatus],
   692                                             true, false);
   693               }
   695               this._callProgressListeners("onStateChange",
   696                                           [aWebProgress, aRequest, aStateFlags, aStatus],
   697                                           false);
   699               if (aStateFlags & (nsIWebProgressListener.STATE_START |
   700                                  nsIWebProgressListener.STATE_STOP)) {
   701                 // reset cached temporary values at beginning and end
   702                 this.mMessage = "";
   703                 this.mTotalProgress = 0;
   704               }
   705               this.mStateFlags = aStateFlags;
   706               this.mStatus = aStatus;
   707             },
   709             onLocationChange: function (aWebProgress, aRequest, aLocation,
   710                                         aFlags) {
   711               // OnLocationChange is called for both the top-level content
   712               // and the subframes.
   713               let topLevel = aWebProgress.isTopLevel;
   715               if (topLevel) {
   716                 // If userTypedClear > 0, the document loaded correctly and we should be
   717                 // clearing the user typed value. We also need to clear the typed value
   718                 // if the document failed to load, to make sure the urlbar reflects the
   719                 // failed URI (particularly for SSL errors). However, don't clear the value
   720                 // if the error page's URI is about:blank, because that causes complete
   721                 // loss of urlbar contents for invalid URI errors (see bug 867957).
   722                 if (this.mBrowser.userTypedClear > 0 ||
   723                     ((aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) &&
   724                      aLocation.spec != "about:blank"))
   725                   this.mBrowser.userTypedValue = null;
   727                 // Clear out the missing plugins list since it's related to the
   728                 // previous location.
   729                 this.mBrowser.missingPlugins = null;
   731                 if (this.mTabBrowser.isFindBarInitialized(this.mTab)) {
   732                   let findBar = this.mTabBrowser.getFindBar(this.mTab);
   734                   // Close the Find toolbar if we're in old-style TAF mode
   735                   if (findBar.findMode != findBar.FIND_NORMAL)
   736                     findBar.close();
   738                   // fix bug 253793 - turn off highlight when page changes
   739                   findBar.getElement("highlight").checked = false;
   740                 }
   742                 // Don't clear the favicon if this onLocationChange was
   743                 // triggered by a pushState or a replaceState.  See bug 550565.
   744                 if (aWebProgress.isLoadingDocument &&
   745                     !(aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_PUSHSTATE)) {
   746                   this.mBrowser.mIconURL = null;
   747                 }
   749                 let autocomplete = this.mTabBrowser._placesAutocomplete;
   750                 let unifiedComplete = this.mTabBrowser._unifiedComplete;
   751                 if (this.mBrowser.registeredOpenURI) {
   752                   autocomplete.unregisterOpenPage(this.mBrowser.registeredOpenURI);
   753                   unifiedComplete.unregisterOpenPage(this.mBrowser.registeredOpenURI);
   754                   delete this.mBrowser.registeredOpenURI;
   755                 }
   756                 // Tabs in private windows aren't registered as "Open" so
   757                 // that they don't appear as switch-to-tab candidates.
   758                 if (!isBlankPageURL(aLocation.spec) &&
   759                     (!PrivateBrowsingUtils.isWindowPrivate(window) ||
   760                     PrivateBrowsingUtils.permanentPrivateBrowsing)) {
   761                   autocomplete.registerOpenPage(aLocation);
   762                   unifiedComplete.registerOpenPage(aLocation);
   763                   this.mBrowser.registeredOpenURI = aLocation;
   764                 }
   765               }
   767               if (!this.mBlank) {
   768                 this._callProgressListeners("onLocationChange",
   769                                             [aWebProgress, aRequest, aLocation,
   770                                              aFlags]);
   771               }
   773               if (topLevel) {
   774                 this.mBrowser.lastURI = aLocation;
   775                 this.mBrowser.lastLocationChange = Date.now();
   776               }
   777             },
   779             onStatusChange: function (aWebProgress, aRequest, aStatus, aMessage) {
   780               if (this.mBlank)
   781                 return;
   783               this._callProgressListeners("onStatusChange",
   784                                           [aWebProgress, aRequest, aStatus, aMessage]);
   786               this.mMessage = aMessage;
   787             },
   789             onSecurityChange: function (aWebProgress, aRequest, aState) {
   790               this._callProgressListeners("onSecurityChange",
   791                                           [aWebProgress, aRequest, aState]);
   792             },
   794             onRefreshAttempted: function (aWebProgress, aURI, aDelay, aSameURI) {
   795               return this._callProgressListeners("onRefreshAttempted",
   796                                                  [aWebProgress, aURI, aDelay, aSameURI]);
   797             },
   799             QueryInterface: function (aIID) {
   800               if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
   801                   aIID.equals(Components.interfaces.nsIWebProgressListener2) ||
   802                   aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
   803                   aIID.equals(Components.interfaces.nsISupports))
   804                 return this;
   805               throw Components.results.NS_NOINTERFACE;
   806             }
   807           });
   808         ]]>
   809         </body>
   810       </method>
   812       <method name="setIcon">
   813         <parameter name="aTab"/>
   814         <parameter name="aURI"/>
   815         <body>
   816           <![CDATA[
   817             var browser = this.getBrowserForTab(aTab);
   818             browser.mIconURL = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
   820             if (aURI && this.mFaviconService) {
   821               if (!(aURI instanceof Ci.nsIURI))
   822                 aURI = makeURI(aURI);
   823               this.mFaviconService.setAndFetchFaviconForPage(browser.currentURI,
   824                                                              aURI, false,
   825                                                              PrivateBrowsingUtils.isWindowPrivate(window) ?
   826                                                                this.mFaviconService.FAVICON_LOAD_PRIVATE :
   827                                                                this.mFaviconService.FAVICON_LOAD_NON_PRIVATE);
   828             }
   830             let sizedIconUrl = browser.mIconURL || "";
   831             if (sizedIconUrl) {
   832               let size = Math.round(16 * window.devicePixelRatio);
   833               sizedIconUrl += (sizedIconUrl.contains("#") ? "&" : "#") +
   834                               "-moz-resolution=" + size + "," + size;
   835             }
   836             if (sizedIconUrl != aTab.getAttribute("image")) {
   837               if (sizedIconUrl)
   838                 aTab.setAttribute("image", sizedIconUrl);
   839               else
   840                 aTab.removeAttribute("image");
   841               this._tabAttrModified(aTab);
   842             }
   844             this._callProgressListeners(browser, "onLinkIconAvailable", [browser.mIconURL]);
   845           ]]>
   846         </body>
   847       </method>
   849       <method name="getIcon">
   850         <parameter name="aTab"/>
   851         <body>
   852           <![CDATA[
   853             let browser = aTab ? this.getBrowserForTab(aTab) : this.selectedBrowser;
   854             return browser.mIconURL;
   855           ]]>
   856         </body>
   857       </method>
   859       <method name="shouldLoadFavIcon">
   860         <parameter name="aURI"/>
   861         <body>
   862           <![CDATA[
   863             return (aURI &&
   864                     Services.prefs.getBoolPref("browser.chrome.site_icons") &&
   865                     Services.prefs.getBoolPref("browser.chrome.favicons") &&
   866                     ("schemeIs" in aURI) && (aURI.schemeIs("http") || aURI.schemeIs("https")));
   867           ]]>
   868         </body>
   869       </method>
   871       <method name="useDefaultIcon">
   872         <parameter name="aTab"/>
   873         <body>
   874           <![CDATA[
   875             var browser = this.getBrowserForTab(aTab);
   876             var documentURI = browser.documentURI;
   877             var icon = null;
   879             if (browser.imageDocument) {
   880               if (Services.prefs.getBoolPref("browser.chrome.site_icons")) {
   881                 let sz = Services.prefs.getIntPref("browser.chrome.image_icons.max_size");
   882                 if (browser.imageDocument.width <= sz &&
   883                     browser.imageDocument.height <= sz) {
   884                   icon = browser.currentURI;
   885                 }
   886               }
   887             }
   888             // Use documentURIObject in the check for shouldLoadFavIcon so that we
   889             // do the right thing with about:-style error pages.  Bug 453442
   890             else if (this.shouldLoadFavIcon(documentURI)) {
   891               let url = documentURI.prePath + "/favicon.ico";
   892               if (!this.isFailedIcon(url))
   893                 icon = url;
   894             }
   895             this.setIcon(aTab, icon);
   896           ]]>
   897         </body>
   898       </method>
   900       <method name="isFailedIcon">
   901         <parameter name="aURI"/>
   902         <body>
   903           <![CDATA[
   904             if (this.mFaviconService) {
   905               if (!(aURI instanceof Ci.nsIURI))
   906                 aURI = makeURI(aURI);
   907               return this.mFaviconService.isFailedFavicon(aURI);
   908             }
   909             return null;
   910           ]]>
   911         </body>
   912       </method>
   914       <method name="getWindowTitleForBrowser">
   915         <parameter name="aBrowser"/>
   916         <body>
   917           <![CDATA[
   918             var newTitle = "";
   919             var docElement = this.ownerDocument.documentElement;
   920             var sep = docElement.getAttribute("titlemenuseparator");
   922             // Strip out any null bytes in the content title, since the
   923             // underlying widget implementations of nsWindow::SetTitle pass
   924             // null-terminated strings to system APIs.
   925             var docTitle = aBrowser.contentTitle.replace("\0", "", "g");
   927             if (!docTitle)
   928               docTitle = docElement.getAttribute("titledefault");
   930             var modifier = docElement.getAttribute("titlemodifier");
   931             if (docTitle) {
   932               newTitle += docElement.getAttribute("titlepreface");
   933               newTitle += docTitle;
   934               if (modifier)
   935                 newTitle += sep;
   936             }
   937             newTitle += modifier;
   939             // If location bar is hidden and the URL type supports a host,
   940             // add the scheme and host to the title to prevent spoofing.
   941             // XXX https://bugzilla.mozilla.org/show_bug.cgi?id=22183#c239
   942             try {
   943               if (docElement.getAttribute("chromehidden").contains("location")) {
   944                 var uri = this.mURIFixup.createExposableURI(
   945                             aBrowser.currentURI);
   946                 if (uri.scheme == "about")
   947                   newTitle = uri.spec + sep + newTitle;
   948                 else
   949                   newTitle = uri.prePath + sep + newTitle;
   950               }
   951             } catch (e) {}
   953             return newTitle;
   954           ]]>
   955         </body>
   956       </method>
   958       <method name="updateTitlebar">
   959         <body>
   960           <![CDATA[
   961             if ("TabView" in window && TabView.isVisible()) {
   962               // ToDo: this will be removed when we gain ability to draw to the menu bar.
   963               // Bug 586175
   964               this.ownerDocument.title = TabView.windowTitle;
   965             }
   966             else {
   967               this.ownerDocument.title = this.getWindowTitleForBrowser(this.mCurrentBrowser);
   968             }
   969           ]]>
   970         </body>
   971       </method>
   973       <method name="updateCurrentBrowser">
   974         <parameter name="aForceUpdate"/>
   975         <body>
   976           <![CDATA[
   977             var newBrowser = this.getBrowserAtIndex(this.tabContainer.selectedIndex);
   978             if (this.mCurrentBrowser == newBrowser && !aForceUpdate)
   979               return;
   981             if (!aForceUpdate) {
   982               TelemetryStopwatch.start("FX_TAB_SWITCH_UPDATE_MS");
   983               window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils)
   984                                                              .beginTabSwitch();
   985             }
   987             var oldTab = this.mCurrentTab;
   989             // Preview mode should not reset the owner
   990             if (!this._previewMode && !oldTab.selected)
   991               oldTab.owner = null;
   993             if (this._lastRelatedTab) {
   994               if (!this._lastRelatedTab.selected)
   995                 this._lastRelatedTab.owner = null;
   996               this._lastRelatedTab = null;
   997             }
   999             var oldBrowser = this.mCurrentBrowser;
  1000             oldBrowser.setAttribute("type", "content-targetable");
  1001             oldBrowser.docShellIsActive = false;
  1003             var updateBlockedPopups = false;
  1004             if ((oldBrowser.blockedPopups && !newBrowser.blockedPopups) ||
  1005                 (!oldBrowser.blockedPopups && newBrowser.blockedPopups))
  1006               updateBlockedPopups = true;
  1008             newBrowser.setAttribute("type", "content-primary");
  1009             newBrowser.docShellIsActive =
  1010               (window.windowState != window.STATE_MINIMIZED);
  1011             this.mCurrentBrowser = newBrowser;
  1012             this.mCurrentTab = this.tabContainer.selectedItem;
  1013             this.showTab(this.mCurrentTab);
  1015             var forwardButtonContainer = document.getElementById("urlbar-wrapper");
  1016             if (forwardButtonContainer) {
  1017               forwardButtonContainer.setAttribute("switchingtabs", "true");
  1018               window.addEventListener("MozAfterPaint", function removeSwitchingtabsAttr() {
  1019                 window.removeEventListener("MozAfterPaint", removeSwitchingtabsAttr);
  1020                 forwardButtonContainer.removeAttribute("switchingtabs");
  1021               });
  1024             this._appendStatusPanel();
  1026             if (updateBlockedPopups)
  1027               this.mCurrentBrowser.updateBlockedPopups(false);
  1029             // Update the URL bar.
  1030             var loc = this.mCurrentBrowser.currentURI;
  1032             // Bug 666809 - SecurityUI support for e10s
  1033             var webProgress = this.mCurrentBrowser.webProgress;
  1034             var securityUI = this.mCurrentBrowser.securityUI;
  1036             this._callProgressListeners(null, "onLocationChange",
  1037                                         [webProgress, null, loc, 0], true,
  1038                                         false);
  1040             if (securityUI) {
  1041               this._callProgressListeners(null, "onSecurityChange",
  1042                                           [webProgress, null, securityUI.state], true, false);
  1045             var listener = this.mTabListeners[this.tabContainer.selectedIndex] || null;
  1046             if (listener && listener.mStateFlags) {
  1047               this._callProgressListeners(null, "onUpdateCurrentBrowser",
  1048                                           [listener.mStateFlags, listener.mStatus,
  1049                                            listener.mMessage, listener.mTotalProgress],
  1050                                           true, false);
  1053             if (!this._previewMode) {
  1054               this.mCurrentTab.removeAttribute("unread");
  1055               oldTab.lastAccessed = Date.now();
  1057               let oldFindBar = oldTab._findBar;
  1058               if (oldFindBar &&
  1059                   oldFindBar.findMode == oldFindBar.FIND_NORMAL &&
  1060                   !oldFindBar.hidden)
  1061                 this._lastFindValue = oldFindBar._findField.value;
  1063               this.updateTitlebar();
  1065               this.mCurrentTab.removeAttribute("titlechanged");
  1068             // If the new tab is busy, and our current state is not busy, then
  1069             // we need to fire a start to all progress listeners.
  1070             const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
  1071             if (this.mCurrentTab.hasAttribute("busy") && !this.mIsBusy) {
  1072               this.mIsBusy = true;
  1073               this._callProgressListeners(null, "onStateChange",
  1074                                           [webProgress, null,
  1075                                            nsIWebProgressListener.STATE_START |
  1076                                            nsIWebProgressListener.STATE_IS_NETWORK, 0],
  1077                                           true, false);
  1080             // If the new tab is not busy, and our current state is busy, then
  1081             // we need to fire a stop to all progress listeners.
  1082             if (!this.mCurrentTab.hasAttribute("busy") && this.mIsBusy) {
  1083               this.mIsBusy = false;
  1084               this._callProgressListeners(null, "onStateChange",
  1085                                           [webProgress, null,
  1086                                            nsIWebProgressListener.STATE_STOP |
  1087                                            nsIWebProgressListener.STATE_IS_NETWORK, 0],
  1088                                           true, false);
  1091             this._setCloseKeyState(!this.mCurrentTab.pinned);
  1093             // TabSelect events are suppressed during preview mode to avoid confusing extensions and other bits of code
  1094             // that might rely upon the other changes suppressed.
  1095             // Focus is suppressed in the event that the main browser window is minimized - focusing a tab would restore the window
  1096             if (!this._previewMode) {
  1097               // We've selected the new tab, so go ahead and notify listeners.
  1098               let event = new CustomEvent("TabSelect", {
  1099                 bubbles: true,
  1100                 cancelable: false,
  1101                 detail: {
  1102                   previousTab: oldTab
  1104               });
  1105               this.mCurrentTab.dispatchEvent(event);
  1107               this._tabAttrModified(oldTab);
  1108               this._tabAttrModified(this.mCurrentTab);
  1110               if (oldBrowser != newBrowser &&
  1111                   oldBrowser.docShell &&
  1112                   oldBrowser.docShell.contentViewer.inPermitUnload) {
  1113                 // Since the user is switching away from a tab that has
  1114                 // a beforeunload prompt active, we remove the prompt.
  1115                 // This prevents confusing user flows like the following:
  1116                 //   1. User attempts to close Firefox
  1117                 //   2. User switches tabs (ingoring a beforeunload prompt)
  1118                 //   3. User returns to tab, presses "Leave page"
  1119                 let promptBox = this.getTabModalPromptBox(oldBrowser);
  1120                 let prompts = promptBox.listPrompts();
  1121                 // NB: This code assumes that the beforeunload prompt
  1122                 //     is the top-most prompt on the tab.
  1123                 promptBox.removePrompt(prompts[prompts.length - 1]);
  1126               // Adjust focus
  1127               oldBrowser._urlbarFocused = (gURLBar && gURLBar.focused);
  1128               if (this.isFindBarInitialized(oldTab)) {
  1129                 let findBar = this.getFindBar(oldTab);
  1130                 oldTab._findBarFocused = (!findBar.hidden &&
  1131                   findBar._findField.getAttribute("focused") == "true");
  1133               do {
  1134                 // When focus is in the tab bar, retain it there.
  1135                 if (document.activeElement == oldTab) {
  1136                   // We need to explicitly focus the new tab, because
  1137                   // tabbox.xml does this only in some cases.
  1138                   this.mCurrentTab.focus();
  1139                   break;
  1142                 // If there's a tabmodal prompt showing, focus it.
  1143                 if (newBrowser.hasAttribute("tabmodalPromptShowing")) {
  1144                   let XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
  1145                   let prompts = newBrowser.parentNode.getElementsByTagNameNS(XUL_NS, "tabmodalprompt");
  1146                   let prompt = prompts[prompts.length - 1];
  1147                   prompt.Dialog.setDefaultFocus();
  1148                   break;
  1151                 // Focus the location bar if it was previously focused for that tab.
  1152                 // In full screen mode, only bother making the location bar visible
  1153                 // if the tab is a blank one.
  1154                 if (newBrowser._urlbarFocused && gURLBar) {
  1156                   // Explicitly close the popup if the URL bar retains focus
  1157                   gURLBar.closePopup();
  1159                   if (!window.fullScreen) {
  1160                     gURLBar.focus();
  1161                     break;
  1162                   } else if (isTabEmpty(this.mCurrentTab)) {
  1163                     focusAndSelectUrlBar();
  1164                     break;
  1168                 // Focus the find bar if it was previously focused for that tab.
  1169                 if (gFindBarInitialized && !gFindBar.hidden &&
  1170                     this.selectedTab._findBarFocused) {
  1171                   gFindBar._findField.focus();
  1172                   break;
  1175                 // Otherwise, focus the content area.
  1176                 let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
  1177                 let focusFlags = fm.FLAG_NOSCROLL;
  1179                 if (!gMultiProcessBrowser) {
  1180                   let newFocusedElement = fm.getFocusedElementForWindow(window.content, true, {});
  1182                   // for anchors, use FLAG_SHOWRING so that it is clear what link was
  1183                   // last clicked when switching back to that tab
  1184                   if (newFocusedElement &&
  1185                       (newFocusedElement instanceof HTMLAnchorElement ||
  1186                        newFocusedElement.getAttributeNS("http://www.w3.org/1999/xlink", "type") == "simple"))
  1187                     focusFlags |= fm.FLAG_SHOWRING;
  1189                 fm.setFocus(newBrowser, focusFlags);
  1190               } while (false);
  1193             this.tabContainer._setPositionalAttributes();
  1195             if (!aForceUpdate)
  1196               TelemetryStopwatch.finish("FX_TAB_SWITCH_UPDATE_MS");
  1197           ]]>
  1198         </body>
  1199       </method>
  1201       <method name="_tabAttrModified">
  1202         <parameter name="aTab"/>
  1203         <body><![CDATA[
  1204           if (aTab.closing)
  1205             return;
  1207           // This event should be dispatched when any of these attributes change:
  1208           // label, crop, busy, image, selected
  1209           var event = document.createEvent("Events");
  1210           event.initEvent("TabAttrModified", true, false);
  1211           aTab.dispatchEvent(event);
  1212         ]]></body>
  1213       </method>
  1215       <method name="setTabTitleLoading">
  1216         <parameter name="aTab"/>
  1217         <body>
  1218           <![CDATA[
  1219             aTab.label = this.mStringBundle.getString("tabs.connecting");
  1220             aTab.crop = "end";
  1221             this._tabAttrModified(aTab);
  1222           ]]>
  1223         </body>
  1224       </method>
  1226       <method name="setTabTitle">
  1227         <parameter name="aTab"/>
  1228         <body>
  1229           <![CDATA[
  1230             var browser = this.getBrowserForTab(aTab);
  1231             var crop = "end";
  1232             var title = browser.contentTitle;
  1234             if (!title) {
  1235               if (browser.currentURI.spec) {
  1236                 try {
  1237                   title = this.mURIFixup.createExposableURI(browser.currentURI).spec;
  1238                 } catch(ex) {
  1239                   title = browser.currentURI.spec;
  1243               if (title && !isBlankPageURL(title)) {
  1244                 // At this point, we now have a URI.
  1245                 // Let's try to unescape it using a character set
  1246                 // in case the URI is not ASCII.
  1247                 try {
  1248                   var characterSet = browser.characterSet;
  1249                   const textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"]
  1250                                                  .getService(Components.interfaces.nsITextToSubURI);
  1251                   title = textToSubURI.unEscapeNonAsciiURI(characterSet, title);
  1252                 } catch(ex) { /* Do nothing. */ }
  1254                 crop = "center";
  1256               } else // Still no title?  Fall back to our untitled string.
  1257                 title = this.mStringBundle.getString("tabs.emptyTabTitle");
  1260             if (aTab.label == title &&
  1261                 aTab.crop == crop)
  1262               return false;
  1264             aTab.label = title;
  1265             aTab.crop = crop;
  1266             this._tabAttrModified(aTab);
  1268             if (aTab.selected)
  1269               this.updateTitlebar();
  1271             return true;
  1272           ]]>
  1273         </body>
  1274       </method>
  1276       <method name="loadOneTab">
  1277         <parameter name="aURI"/>
  1278         <parameter name="aReferrerURI"/>
  1279         <parameter name="aCharset"/>
  1280         <parameter name="aPostData"/>
  1281         <parameter name="aLoadInBackground"/>
  1282         <parameter name="aAllowThirdPartyFixup"/>
  1283         <body>
  1284           <![CDATA[
  1285             var aFromExternal;
  1286             var aRelatedToCurrent;
  1287             var aDisableMCB;
  1288             var aSkipAnimation;
  1289             if (arguments.length == 2 &&
  1290                 typeof arguments[1] == "object" &&
  1291                 !(arguments[1] instanceof Ci.nsIURI)) {
  1292               let params = arguments[1];
  1293               aReferrerURI          = params.referrerURI;
  1294               aCharset              = params.charset;
  1295               aPostData             = params.postData;
  1296               aLoadInBackground     = params.inBackground;
  1297               aAllowThirdPartyFixup = params.allowThirdPartyFixup;
  1298               aFromExternal         = params.fromExternal;
  1299               aRelatedToCurrent     = params.relatedToCurrent;
  1300               aDisableMCB           = params.disableMCB;
  1301               aSkipAnimation        = params.skipAnimation;
  1304             var bgLoad = (aLoadInBackground != null) ? aLoadInBackground :
  1305                          Services.prefs.getBoolPref("browser.tabs.loadInBackground");
  1306             var owner = bgLoad ? null : this.selectedTab;
  1307             var tab = this.addTab(aURI, {
  1308                                   referrerURI: aReferrerURI,
  1309                                   charset: aCharset,
  1310                                   postData: aPostData,
  1311                                   ownerTab: owner,
  1312                                   allowThirdPartyFixup: aAllowThirdPartyFixup,
  1313                                   fromExternal: aFromExternal,
  1314                                   relatedToCurrent: aRelatedToCurrent,
  1315                                   skipAnimation: aSkipAnimation,
  1316                                   disableMCB: aDisableMCB});
  1317             if (!bgLoad)
  1318               this.selectedTab = tab;
  1320             return tab;
  1321          ]]>
  1322         </body>
  1323       </method>
  1325       <method name="loadTabs">
  1326         <parameter name="aURIs"/>
  1327         <parameter name="aLoadInBackground"/>
  1328         <parameter name="aReplace"/>
  1329         <body><![CDATA[
  1330           if (!aURIs.length)
  1331             return;
  1333           // The tab selected after this new tab is closed (i.e. the new tab's
  1334           // "owner") is the next adjacent tab (i.e. not the previously viewed tab)
  1335           // when several urls are opened here (i.e. closing the first should select
  1336           // the next of many URLs opened) or if the pref to have UI links opened in
  1337           // the background is set (i.e. the link is not being opened modally)
  1338           //
  1339           // i.e.
  1340           //    Number of URLs    Load UI Links in BG       Focus Last Viewed?
  1341           //    == 1              false                     YES
  1342           //    == 1              true                      NO
  1343           //    > 1               false/true                NO
  1344           var multiple = aURIs.length > 1;
  1345           var owner = multiple || aLoadInBackground ? null : this.selectedTab;
  1346           var firstTabAdded = null;
  1348           if (aReplace) {
  1349             try {
  1350               this.loadURI(aURIs[0], null, null);
  1351             } catch (e) {
  1352               // Ignore failure in case a URI is wrong, so we can continue
  1353               // opening the next ones.
  1356           else
  1357             firstTabAdded = this.addTab(aURIs[0], {ownerTab: owner, skipAnimation: multiple});
  1359           var tabNum = this.tabContainer.selectedIndex;
  1360           for (let i = 1; i < aURIs.length; ++i) {
  1361             let tab = this.addTab(aURIs[i], {skipAnimation: true});
  1362             if (aReplace)
  1363               this.moveTabTo(tab, ++tabNum);
  1366           if (!aLoadInBackground) {
  1367             if (firstTabAdded) {
  1368               // .selectedTab setter focuses the content area
  1369               this.selectedTab = firstTabAdded;
  1371             else
  1372               this.selectedBrowser.focus();
  1374         ]]></body>
  1375       </method>
  1377 #ifdef MAKE_E10S_WORK
  1378       <method name="updateBrowserRemoteness">
  1379         <parameter name="aBrowser"/>
  1380         <parameter name="aURL"/>
  1381         <body>
  1382           <![CDATA[
  1383             let shouldBeRemote = this._shouldBrowserBeRemote(aURL);
  1385             let isRemote = aBrowser.getAttribute("remote") == "true";
  1386             if (isRemote == shouldBeRemote)
  1387               return false;
  1389             let wasActive = document.activeElement == aBrowser;
  1391             // Unhook our progress listener.
  1392             let tab = this._getTabForBrowser(aBrowser);
  1393             let index = tab._tPos;
  1394             let filter = this.mTabFilters[index];
  1395             aBrowser.webProgress.removeProgressListener(filter);
  1397             // Change the "remote" attribute.
  1398             let parent = aBrowser.parentNode;
  1399             parent.removeChild(aBrowser);
  1400             aBrowser.setAttribute("remote", shouldBeRemote ? "true" : "false");
  1401             parent.appendChild(aBrowser);
  1403             // Restore the progress listener.
  1404             aBrowser.webProgress.addProgressListener(filter, Ci.nsIWebProgress.NOTIFY_ALL);
  1406             if (shouldBeRemote)
  1407               tab.setAttribute("remote", "true");
  1408             else
  1409               tab.removeAttribute("remote");
  1411             if (wasActive)
  1412               aBrowser.focus();
  1414             return true;
  1415           ]]>
  1416         </body>
  1417       </method>
  1419       <!--
  1420         Returns true if we want to load the content for this URL in a
  1421         remote process. Eventually this should just check whether aURL
  1422         is unprivileged. Right now, though, we would like to load
  1423         some unprivileged URLs (like about:neterror) in the main
  1424         process since they interact with chrome code through
  1425         BrowserOnClick.
  1426       -->
  1427       <method name="_shouldBrowserBeRemote">
  1428         <parameter name="aURL"/>
  1429         <body>
  1430           <![CDATA[
  1431             if (!gMultiProcessBrowser)
  1432               return false;
  1434             // loadURI in browser.xml treats null as about:blank
  1435             if (!aURL)
  1436               aURL = "about:blank";
  1438             if (aURL.startsWith("about:") &&
  1439                 aURL.toLowerCase() != "about:home" &&
  1440                 aURL.toLowerCase() != "about:blank") {
  1441               return false;
  1444             if (aURL.startsWith("chrome:"))
  1445               return false;
  1447             return true;
  1448           ]]>
  1449         </body>
  1450       </method>
  1451 #endif
  1453       <method name="addTab">
  1454         <parameter name="aURI"/>
  1455         <parameter name="aReferrerURI"/>
  1456         <parameter name="aCharset"/>
  1457         <parameter name="aPostData"/>
  1458         <parameter name="aOwner"/>
  1459         <parameter name="aAllowThirdPartyFixup"/>
  1460         <body>
  1461           <![CDATA[
  1462             const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
  1463             var aFromExternal;
  1464             var aRelatedToCurrent;
  1465             var aSkipAnimation;
  1466             var aDisableMCB;
  1467             if (arguments.length == 2 &&
  1468                 typeof arguments[1] == "object" &&
  1469                 !(arguments[1] instanceof Ci.nsIURI)) {
  1470               let params = arguments[1];
  1471               aReferrerURI          = params.referrerURI;
  1472               aCharset              = params.charset;
  1473               aPostData             = params.postData;
  1474               aOwner                = params.ownerTab;
  1475               aAllowThirdPartyFixup = params.allowThirdPartyFixup;
  1476               aFromExternal         = params.fromExternal;
  1477               aRelatedToCurrent     = params.relatedToCurrent;
  1478               aSkipAnimation        = params.skipAnimation;
  1479               aDisableMCB           = params.disableMCB;
  1482             // if we're adding tabs, we're past interrupt mode, ditch the owner
  1483             if (this.mCurrentTab.owner)
  1484               this.mCurrentTab.owner = null;
  1486             var t = document.createElementNS(NS_XUL, "tab");
  1488             var uriIsAboutBlank = !aURI || aURI == "about:blank";
  1490             t.setAttribute("crop", "end");
  1491             t.setAttribute("onerror", "this.removeAttribute('image');");
  1492             t.className = "tabbrowser-tab";
  1494 #ifdef MAKE_E10S_WORK
  1495             let remote = this._shouldBrowserBeRemote(aURI);
  1496 #else
  1497             let remote = gMultiProcessBrowser;
  1498 #endif
  1499             if (remote)
  1500               t.setAttribute("remote", "true");
  1502             this.tabContainer._unlockTabSizing();
  1504             // When overflowing, new tabs are scrolled into view smoothly, which
  1505             // doesn't go well together with the width transition. So we skip the
  1506             // transition in that case.
  1507             let animate = !aSkipAnimation &&
  1508                           this.tabContainer.getAttribute("overflow") != "true" &&
  1509                           Services.prefs.getBoolPref("browser.tabs.animate");
  1510             if (!animate) {
  1511               t.setAttribute("fadein", "true");
  1512               setTimeout(function (tabContainer) {
  1513                 tabContainer._handleNewTab(t);
  1514               }, 0, this.tabContainer);
  1517             // invalidate caches
  1518             this._browsers = null;
  1519             this._visibleTabs = null;
  1521             this.tabContainer.appendChild(t);
  1523             // If this new tab is owned by another, assert that relationship
  1524             if (aOwner)
  1525               t.owner = aOwner;
  1527             var b = document.createElementNS(
  1528               "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
  1529                                              "browser");
  1530             b.setAttribute("type", "content-targetable");
  1531             b.setAttribute("message", "true");
  1532             b.setAttribute("contextmenu", this.getAttribute("contentcontextmenu"));
  1533             b.setAttribute("tooltip", this.getAttribute("contenttooltip"));
  1535             if (remote)
  1536               b.setAttribute("remote", "true");
  1538             if (window.gShowPageResizers && window.windowState == window.STATE_NORMAL) {
  1539               b.setAttribute("showresizer", "true");
  1542             if (this.hasAttribute("autocompletepopup"))
  1543               b.setAttribute("autocompletepopup", this.getAttribute("autocompletepopup"));
  1545             if (this.hasAttribute("selectpopup"))
  1546               b.setAttribute("selectpopup", this.getAttribute("selectpopup"));
  1548             b.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
  1550             // Create the browserStack container
  1551             var stack = document.createElementNS(NS_XUL, "stack");
  1552             stack.className = "browserStack";
  1553             stack.appendChild(b);
  1554             stack.setAttribute("flex", "1");
  1556             // Create the browserContainer
  1557             var browserContainer = document.createElementNS(NS_XUL, "vbox");
  1558             browserContainer.className = "browserContainer";
  1559             browserContainer.appendChild(stack);
  1560             browserContainer.setAttribute("flex", "1");
  1562             // Create the sidebar container
  1563             var browserSidebarContainer = document.createElementNS(NS_XUL,
  1564                                                                    "hbox");
  1565             browserSidebarContainer.className = "browserSidebarContainer";
  1566             browserSidebarContainer.appendChild(browserContainer);
  1567             browserSidebarContainer.setAttribute("flex", "1");
  1569             // Add the Message and the Browser to the box
  1570             var notificationbox = document.createElementNS(NS_XUL,
  1571                                                            "notificationbox");
  1572             notificationbox.setAttribute("flex", "1");
  1573             notificationbox.appendChild(browserSidebarContainer);
  1575             var position = this.tabs.length - 1;
  1576             var uniqueId = this._generateUniquePanelID();
  1577             notificationbox.id = uniqueId;
  1578             t.linkedPanel = uniqueId;
  1579             t.linkedBrowser = b;
  1580             t._tPos = position;
  1581             this.tabContainer._setPositionalAttributes();
  1583             // Prevent the superfluous initial load of a blank document
  1584             // if we're going to load something other than about:blank.
  1585             if (!uriIsAboutBlank) {
  1586               b.setAttribute("nodefaultsrc", "true");
  1589             // NB: this appendChild call causes us to run constructors for the
  1590             // browser element, which fires off a bunch of notifications. Some
  1591             // of those notifications can cause code to run that inspects our
  1592             // state, so it is important that the tab element is fully
  1593             // initialized by this point.
  1594             this.mPanelContainer.appendChild(notificationbox);
  1596             // We've waited until the tab is in the DOM to set the label. This
  1597             // allows the TabLabelModified event to be properly dispatched.
  1598             if (!aURI || isBlankPageURL(aURI)) {
  1599               t.label = this.mStringBundle.getString("tabs.emptyTabTitle");
  1600             } else {
  1601               t.label = aURI;
  1604             this.tabContainer.updateVisibility();
  1606             // wire up a progress listener for the new browser object.
  1607             var tabListener = this.mTabProgressListener(t, b, uriIsAboutBlank);
  1608             const filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
  1609                                      .createInstance(Components.interfaces.nsIWebProgress);
  1610             filter.addProgressListener(tabListener, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
  1611             b.webProgress.addProgressListener(filter, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
  1612             this.mTabListeners[position] = tabListener;
  1613             this.mTabFilters[position] = filter;
  1615             b.droppedLinkHandler = handleDroppedLink;
  1617             // If we just created a new tab that loads the default
  1618             // newtab url, swap in a preloaded page if possible.
  1619             // Do nothing if we're a private window.
  1620             let docShellsSwapped = false;
  1621             if (aURI == BROWSER_NEW_TAB_URL &&
  1622                 !PrivateBrowsingUtils.isWindowPrivate(window) &&
  1623                 !gMultiProcessBrowser) {
  1624               docShellsSwapped = gBrowserNewTabPreloader.newTab(t);
  1625             } else if (aURI == "about:customizing") {
  1626               docShellsSwapped = gCustomizationTabPreloader.newTab(t);
  1629             // Dispatch a new tab notification.  We do this once we're
  1630             // entirely done, so that things are in a consistent state
  1631             // even if the event listener opens or closes tabs.
  1632             var evt = document.createEvent("Events");
  1633             evt.initEvent("TabOpen", true, false);
  1634             t.dispatchEvent(evt);
  1636             // If we didn't swap docShells with a preloaded browser
  1637             // then let's just continue loading the page normally.
  1638             if (!docShellsSwapped && !uriIsAboutBlank) {
  1639               // pretend the user typed this so it'll be available till
  1640               // the document successfully loads
  1641               if (aURI && gInitialPages.indexOf(aURI) == -1)
  1642                 b.userTypedValue = aURI;
  1644               let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
  1645               if (aAllowThirdPartyFixup) {
  1646                 flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
  1647                 flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
  1649               if (aFromExternal)
  1650                 flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL;
  1651               if (aDisableMCB)
  1652                 flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_MIXED_CONTENT;
  1653               try {
  1654                 b.loadURIWithFlags(aURI, flags, aReferrerURI, aCharset, aPostData);
  1655               } catch (ex) {
  1656                 Cu.reportError(ex);
  1660             // We start our browsers out as inactive, and then maintain
  1661             // activeness in the tab switcher.
  1662             b.docShellIsActive = false;
  1664             // Check if we're opening a tab related to the current tab and
  1665             // move it to after the current tab.
  1666             // aReferrerURI is null or undefined if the tab is opened from
  1667             // an external application or bookmark, i.e. somewhere other
  1668             // than the current tab.
  1669             if ((aRelatedToCurrent == null ? aReferrerURI : aRelatedToCurrent) &&
  1670                 Services.prefs.getBoolPref("browser.tabs.insertRelatedAfterCurrent")) {
  1671               let newTabPos = (this._lastRelatedTab ||
  1672                                this.selectedTab)._tPos + 1;
  1673               if (this._lastRelatedTab)
  1674                 this._lastRelatedTab.owner = null;
  1675               else
  1676                 t.owner = this.selectedTab;
  1677               this.moveTabTo(t, newTabPos);
  1678               this._lastRelatedTab = t;
  1681             if (animate) {
  1682               mozRequestAnimationFrame(function () {
  1683                 this.tabContainer._handleTabTelemetryStart(t, aURI);
  1685                 // kick the animation off
  1686                 t.setAttribute("fadein", "true");
  1687               }.bind(this));
  1690             return t;
  1691           ]]>
  1692         </body>
  1693       </method>
  1695       <method name="warnAboutClosingTabs">
  1696       <parameter name="aCloseTabs"/>
  1697       <parameter name="aTab"/>
  1698       <body>
  1699         <![CDATA[
  1700           var tabsToClose;
  1701           switch (aCloseTabs) {
  1702             case this.closingTabsEnum.ALL:
  1703               tabsToClose = this.tabs.length - this._removingTabs.length -
  1704                             gBrowser._numPinnedTabs;
  1705               break;
  1706             case this.closingTabsEnum.OTHER:
  1707               tabsToClose = this.visibleTabs.length - 1 - gBrowser._numPinnedTabs;
  1708               break;
  1709             case this.closingTabsEnum.TO_END:
  1710               if (!aTab)
  1711                 throw new Error("Required argument missing: aTab");
  1713               tabsToClose = this.getTabsToTheEndFrom(aTab).length;
  1714               break;
  1715             default:
  1716               throw new Error("Invalid argument: " + aCloseTabs);
  1719           if (tabsToClose <= 1)
  1720             return true;
  1722           const pref = aCloseTabs == this.closingTabsEnum.ALL ?
  1723                        "browser.tabs.warnOnClose" : "browser.tabs.warnOnCloseOtherTabs";
  1724           var shouldPrompt = Services.prefs.getBoolPref(pref);
  1725           if (!shouldPrompt)
  1726             return true;
  1728           var ps = Services.prompt;
  1730           // default to true: if it were false, we wouldn't get this far
  1731           var warnOnClose = { value: true };
  1732           var bundle = this.mStringBundle;
  1734           // focus the window before prompting.
  1735           // this will raise any minimized window, which will
  1736           // make it obvious which window the prompt is for and will
  1737           // solve the problem of windows "obscuring" the prompt.
  1738           // see bug #350299 for more details
  1739           window.focus();
  1740           var warningMessage =
  1741             PluralForm.get(tabsToClose, bundle.getString("tabs.closeWarningMultiple"))
  1742                       .replace("#1", tabsToClose);
  1743           var buttonPressed =
  1744             ps.confirmEx(window,
  1745                          bundle.getString("tabs.closeWarningTitle"),
  1746                          warningMessage,
  1747                          (ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_0)
  1748                          + (ps.BUTTON_TITLE_CANCEL * ps.BUTTON_POS_1),
  1749                          bundle.getString("tabs.closeButtonMultiple"),
  1750                          null, null,
  1751                          aCloseTabs == this.closingTabsEnum.ALL ?
  1752                            bundle.getString("tabs.closeWarningPromptMe") : null,
  1753                          warnOnClose);
  1754           var reallyClose = (buttonPressed == 0);
  1756           // don't set the pref unless they press OK and it's false
  1757           if (aCloseTabs == this.closingTabsEnum.ALL && reallyClose && !warnOnClose.value)
  1758             Services.prefs.setBoolPref(pref, false);
  1760           return reallyClose;
  1761         ]]>
  1762       </body>
  1763       </method>
  1765       <method name="getTabsToTheEndFrom">
  1766         <parameter name="aTab"/>
  1767         <body>
  1768           <![CDATA[
  1769             var tabsToEnd = [];
  1770             let tabs = this.visibleTabs;
  1771             for (let i = tabs.length - 1; tabs[i] != aTab && i >= 0; --i) {
  1772               tabsToEnd.push(tabs[i]);
  1774             return tabsToEnd.reverse();
  1775           ]]>
  1776         </body>
  1777       </method>
  1779       <method name="removeTabsToTheEndFrom">
  1780         <parameter name="aTab"/>
  1781         <body>
  1782           <![CDATA[
  1783             if (this.warnAboutClosingTabs(this.closingTabsEnum.TO_END, aTab)) {
  1784               let tabs = this.getTabsToTheEndFrom(aTab);
  1785               for (let i = tabs.length - 1; i >= 0; --i) {
  1786                 this.removeTab(tabs[i], {animate: true});
  1789           ]]>
  1790         </body>
  1791       </method>
  1793       <method name="removeAllTabsBut">
  1794         <parameter name="aTab"/>
  1795         <body>
  1796           <![CDATA[
  1797             if (aTab.pinned)
  1798               return;
  1800             if (this.warnAboutClosingTabs(this.closingTabsEnum.OTHER)) {
  1801               let tabs = this.visibleTabs;
  1802               this.selectedTab = aTab;
  1804               for (let i = tabs.length - 1; i >= 0; --i) {
  1805                 if (tabs[i] != aTab && !tabs[i].pinned)
  1806                   this.removeTab(tabs[i], {animate: true});
  1809           ]]>
  1810         </body>
  1811       </method>
  1813       <method name="removeCurrentTab">
  1814         <parameter name="aParams"/>
  1815         <body>
  1816           <![CDATA[
  1817             this.removeTab(this.mCurrentTab, aParams);
  1818           ]]>
  1819         </body>
  1820       </method>
  1822       <field name="_removingTabs">
  1823         []
  1824       </field>
  1826       <method name="removeTab">
  1827         <parameter name="aTab"/>
  1828         <parameter name="aParams"/>
  1829         <body>
  1830           <![CDATA[
  1831             if (aParams) {
  1832               var animate = aParams.animate;
  1833               var byMouse = aParams.byMouse;
  1836             // Handle requests for synchronously removing an already
  1837             // asynchronously closing tab.
  1838             if (!animate &&
  1839                 aTab.closing) {
  1840               this._endRemoveTab(aTab);
  1841               return;
  1844             var isLastTab = (this.tabs.length - this._removingTabs.length == 1);
  1846             if (!this._beginRemoveTab(aTab, false, null, true))
  1847               return;
  1849             if (!aTab.pinned && !aTab.hidden && aTab._fullyOpen && byMouse)
  1850               this.tabContainer._lockTabSizing(aTab);
  1851             else
  1852               this.tabContainer._unlockTabSizing();
  1854             if (!animate /* the caller didn't opt in */ ||
  1855                 isLastTab ||
  1856                 aTab.pinned ||
  1857                 aTab.hidden ||
  1858                 this._removingTabs.length > 3 /* don't want lots of concurrent animations */ ||
  1859                 aTab.getAttribute("fadein") != "true" /* fade-in transition hasn't been triggered yet */ ||
  1860                 window.getComputedStyle(aTab).maxWidth == "0.1px" /* fade-in transition hasn't moved yet */ ||
  1861                 !Services.prefs.getBoolPref("browser.tabs.animate")) {
  1862               this._endRemoveTab(aTab);
  1863               return;
  1866             this.tabContainer._handleTabTelemetryStart(aTab);
  1868             this._blurTab(aTab);
  1869             aTab.style.maxWidth = ""; // ensure that fade-out transition happens
  1870             aTab.removeAttribute("fadein");
  1872             setTimeout(function (tab, tabbrowser) {
  1873               if (tab.parentNode &&
  1874                   window.getComputedStyle(tab).maxWidth == "0.1px") {
  1875                 NS_ASSERT(false, "Giving up waiting for the tab closing animation to finish (bug 608589)");
  1876                 tabbrowser._endRemoveTab(tab);
  1878             }, 3000, aTab, this);
  1879           ]]>
  1880         </body>
  1881       </method>
  1883       <!-- Tab close requests are ignored if the window is closing anyway,
  1884            e.g. when holding Ctrl+W. -->
  1885       <field name="_windowIsClosing">
  1886         false
  1887       </field>
  1889       <method name="_beginRemoveTab">
  1890         <parameter name="aTab"/>
  1891         <parameter name="aTabWillBeMoved"/>
  1892         <parameter name="aCloseWindowWithLastTab"/>
  1893         <parameter name="aCloseWindowFastpath"/>
  1894         <body>
  1895           <![CDATA[
  1896             if (aTab.closing ||
  1897                 this._windowIsClosing)
  1898               return false;
  1900             var browser = this.getBrowserForTab(aTab);
  1902             if (!aTabWillBeMoved) {
  1903               let ds = browser.docShell;
  1904               if (ds &&
  1905                   ds.contentViewer &&
  1906                   !ds.contentViewer.permitUnload()) {
  1907                 return false;
  1911             var closeWindow = false;
  1912             var newTab = false;
  1913             if (this.tabs.length - this._removingTabs.length == 1) {
  1914               closeWindow = aCloseWindowWithLastTab != null ? aCloseWindowWithLastTab :
  1915                             !window.toolbar.visible ||
  1916                               Services.prefs.getBoolPref("browser.tabs.closeWindowWithLastTab");
  1918               // Closing the tab and replacing it with a blank one is notably slower
  1919               // than closing the window right away. If the caller opts in, take
  1920               // the fast path.
  1921               if (closeWindow &&
  1922                   aCloseWindowFastpath &&
  1923                   this._removingTabs.length == 0) {
  1924                 // This call actually closes the window, unless the user
  1925                 // cancels the operation.  We are finished here in both cases.
  1926                 this._windowIsClosing = window.closeWindow(true, window.warnAboutClosingWindow);
  1927                 return null;
  1930               newTab = true;
  1933             aTab.closing = true;
  1934             this._removingTabs.push(aTab);
  1935             this._visibleTabs = null; // invalidate cache
  1937             // Invalidate hovered tab state tracking for this closing tab.
  1938             if (this.tabContainer._hoveredTab == aTab)
  1939               aTab._mouseleave();
  1941             if (newTab)
  1942               this.addTab(BROWSER_NEW_TAB_URL, {skipAnimation: true});
  1943             else
  1944               this.tabContainer.updateVisibility();
  1946             // We're committed to closing the tab now.
  1947             // Dispatch a notification.
  1948             // We dispatch it before any teardown so that event listeners can
  1949             // inspect the tab that's about to close.
  1950             var evt = document.createEvent("UIEvent");
  1951             evt.initUIEvent("TabClose", true, false, window, aTabWillBeMoved ? 1 : 0);
  1952             aTab.dispatchEvent(evt);
  1954             if (!aTabWillBeMoved && !gMultiProcessBrowser) {
  1955               // Prevent this tab from showing further dialogs, since we're closing it
  1956               var windowUtils = browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).
  1957                                 getInterface(Ci.nsIDOMWindowUtils);
  1958               windowUtils.disableDialogs();
  1961             // Remove the tab's filter and progress listener.
  1962             const filter = this.mTabFilters[aTab._tPos];
  1964             browser.webProgress.removeProgressListener(filter);
  1966             filter.removeProgressListener(this.mTabListeners[aTab._tPos]);
  1967             this.mTabListeners[aTab._tPos].destroy();
  1969             if (browser.registeredOpenURI && !aTabWillBeMoved) {
  1970               this._placesAutocomplete.unregisterOpenPage(browser.registeredOpenURI);
  1971               this._unifiedComplete.unregisterOpenPage(browser.registeredOpenURI);
  1972               delete browser.registeredOpenURI;
  1975             // We are no longer the primary content area.
  1976             browser.setAttribute("type", "content-targetable");
  1978             // Remove this tab as the owner of any other tabs, since it's going away.
  1979             Array.forEach(this.tabs, function (tab) {
  1980               if ("owner" in tab && tab.owner == aTab)
  1981                 // |tab| is a child of the tab we're removing, make it an orphan
  1982                 tab.owner = null;
  1983             });
  1985             aTab._endRemoveArgs = [closeWindow, newTab];
  1986             return true;
  1987           ]]>
  1988         </body>
  1989       </method>
  1991       <method name="_endRemoveTab">
  1992         <parameter name="aTab"/>
  1993         <body>
  1994           <![CDATA[
  1995             if (!aTab || !aTab._endRemoveArgs)
  1996               return;
  1998             var [aCloseWindow, aNewTab] = aTab._endRemoveArgs;
  1999             aTab._endRemoveArgs = null;
  2001             if (this._windowIsClosing) {
  2002               aCloseWindow = false;
  2003               aNewTab = false;
  2006             this._lastRelatedTab = null;
  2008             // update the UI early for responsiveness
  2009             aTab.collapsed = true;
  2010             this.tabContainer._fillTrailingGap();
  2011             this._blurTab(aTab);
  2013             this._removingTabs.splice(this._removingTabs.indexOf(aTab), 1);
  2015             if (aCloseWindow) {
  2016               this._windowIsClosing = true;
  2017               while (this._removingTabs.length)
  2018                 this._endRemoveTab(this._removingTabs[0]);
  2019             } else if (!this._windowIsClosing) {
  2020               if (aNewTab)
  2021                 focusAndSelectUrlBar();
  2023               // workaround for bug 345399
  2024               this.tabContainer.mTabstrip._updateScrollButtonsDisabledState();
  2027             // We're going to remove the tab and the browser now.
  2028             // Clean up mTabFilters and mTabListeners now rather than in
  2029             // _beginRemoveTab, so that their size is always in sync with the
  2030             // number of tabs and browsers (the xbl destructor depends on this).
  2031             this.mTabFilters.splice(aTab._tPos, 1);
  2032             this.mTabListeners.splice(aTab._tPos, 1);
  2034             var browser = this.getBrowserForTab(aTab);
  2036             // Because of the way XBL works (fields just set JS
  2037             // properties on the element) and the code we have in place
  2038             // to preserve the JS objects for any elements that have
  2039             // JS properties set on them, the browser element won't be
  2040             // destroyed until the document goes away.  So we force a
  2041             // cleanup ourselves.
  2042             // This has to happen before we remove the child so that the
  2043             // XBL implementation of nsIObserver still works.
  2044             browser.destroy();
  2046             var wasPinned = aTab.pinned;
  2048             // Invalidate browsers cache, as the tab is removed from the
  2049             // tab container.
  2050             this._browsers = null;
  2052             // Remove the tab ...
  2053             this.tabContainer.removeChild(aTab);
  2055             // ... and fix up the _tPos properties immediately.
  2056             for (let i = aTab._tPos; i < this.tabs.length; i++)
  2057               this.tabs[i]._tPos = i;
  2059             if (!this._windowIsClosing) {
  2060               if (wasPinned)
  2061                 this.tabContainer._positionPinnedTabs();
  2063               // update tab close buttons state
  2064               this.tabContainer.adjustTabstrip();
  2066               setTimeout(function(tabs) {
  2067                 tabs._lastTabClosedByMouse = false;
  2068               }, 0, this.tabContainer);
  2071             // update tab positional properties and attributes
  2072             this.selectedTab._selected = true;
  2073             this.tabContainer._setPositionalAttributes();
  2075             // Removing the panel requires fixing up selectedPanel immediately
  2076             // (see below), which would be hindered by the potentially expensive
  2077             // browser removal. So we remove the browser and the panel in two
  2078             // steps.
  2080             var panel = this.getNotificationBox(browser);
  2082             // This will unload the document. An unload handler could remove
  2083             // dependant tabs, so it's important that the tabbrowser is now in
  2084             // a consistent state (tab removed, tab positions updated, etc.).
  2085             browser.parentNode.removeChild(browser);
  2087             // Release the browser in case something is erroneously holding a
  2088             // reference to the tab after its removal.
  2089             aTab.linkedBrowser = null;
  2091             // As the browser is removed, the removal of a dependent document can
  2092             // cause the whole window to close. So at this point, it's possible
  2093             // that the binding is destructed.
  2094             if (this.mTabBox) {
  2095               let selectedPanel = this.mTabBox.selectedPanel;
  2097               this.mPanelContainer.removeChild(panel);
  2099               // Under the hood, a selectedIndex attribute controls which panel
  2100               // is displayed. Removing a panel A which precedes the selected
  2101               // panel B makes selectedIndex point to the panel next to B. We
  2102               // need to explicitly preserve B as the selected panel.
  2103               this.mTabBox.selectedPanel = selectedPanel;
  2106             if (aCloseWindow)
  2107               this._windowIsClosing = closeWindow(true, window.warnAboutClosingWindow);
  2108           ]]>
  2109         </body>
  2110       </method>
  2112       <method name="_blurTab">
  2113         <parameter name="aTab"/>
  2114         <body>
  2115           <![CDATA[
  2116             if (!aTab.selected)
  2117               return;
  2119             if (aTab.owner &&
  2120                 !aTab.owner.hidden &&
  2121                 !aTab.owner.closing &&
  2122                 Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose")) {
  2123               this.selectedTab = aTab.owner;
  2124               return;
  2127             // Switch to a visible tab unless there aren't any others remaining
  2128             let remainingTabs = this.visibleTabs;
  2129             let numTabs = remainingTabs.length;
  2130             if (numTabs == 0 || numTabs == 1 && remainingTabs[0] == aTab) {
  2131               remainingTabs = Array.filter(this.tabs, function(tab) {
  2132                 return !tab.closing;
  2133               }, this);
  2136             // Try to find a remaining tab that comes after the given tab
  2137             var tab = aTab;
  2138             do {
  2139               tab = tab.nextSibling;
  2140             } while (tab && remainingTabs.indexOf(tab) == -1);
  2142             if (!tab) {
  2143               tab = aTab;
  2145               do {
  2146                 tab = tab.previousSibling;
  2147               } while (tab && remainingTabs.indexOf(tab) == -1);
  2150             this.selectedTab = tab;
  2151           ]]>
  2152         </body>
  2153       </method>
  2155       <method name="swapNewTabWithBrowser">
  2156         <parameter name="aNewTab"/>
  2157         <parameter name="aBrowser"/>
  2158         <body>
  2159           <![CDATA[
  2160             // The browser must be standalone.
  2161             if (aBrowser.getTabBrowser())
  2162               throw Cr.NS_ERROR_INVALID_ARG;
  2164             // The tab is definitely not loading.
  2165             aNewTab.removeAttribute("busy");
  2166             if (aNewTab.selected) {
  2167               this.mIsBusy = false;
  2170             this._swapBrowserDocShells(aNewTab, aBrowser);
  2172             // Update the new tab's title.
  2173             this.setTabTitle(aNewTab);
  2175             if (aNewTab.selected) {
  2176               this.updateCurrentBrowser(true);
  2178           ]]>
  2179         </body>
  2180       </method>
  2182       <method name="swapBrowsersAndCloseOther">
  2183         <parameter name="aOurTab"/>
  2184         <parameter name="aOtherTab"/>
  2185         <body>
  2186           <![CDATA[
  2187             // Do not allow transfering a private tab to a non-private window
  2188             // and vice versa.
  2189             if (PrivateBrowsingUtils.isWindowPrivate(window) !=
  2190                 PrivateBrowsingUtils.isWindowPrivate(aOtherTab.ownerDocument.defaultView))
  2191               return;
  2193             // That's gBrowser for the other window, not the tab's browser!
  2194             var remoteBrowser = aOtherTab.ownerDocument.defaultView.gBrowser;
  2195             var isPending = aOtherTab.hasAttribute("pending");
  2197             // First, start teardown of the other browser.  Make sure to not
  2198             // fire the beforeunload event in the process.  Close the other
  2199             // window if this was its last tab.
  2200             if (!remoteBrowser._beginRemoveTab(aOtherTab, true, true))
  2201               return;
  2203             let ourBrowser = this.getBrowserForTab(aOurTab);
  2204             let otherBrowser = aOtherTab.linkedBrowser;
  2206             // If the other tab is pending (i.e. has not been restored, yet)
  2207             // then do not switch docShells but retrieve the other tab's state
  2208             // and apply it to our tab.
  2209             if (isPending) {
  2210               SessionStore.setTabState(aOurTab, SessionStore.getTabState(aOtherTab));
  2212               // Make sure to unregister any open URIs.
  2213               this._swapRegisteredOpenURIs(ourBrowser, otherBrowser);
  2214             } else {
  2215               // Workarounds for bug 458697
  2216               // Icon might have been set on DOMLinkAdded, don't override that.
  2217               if (!ourBrowser.mIconURL && otherBrowser.mIconURL)
  2218                 this.setIcon(aOurTab, otherBrowser.mIconURL);
  2219               var isBusy = aOtherTab.hasAttribute("busy");
  2220               if (isBusy) {
  2221                 aOurTab.setAttribute("busy", "true");
  2222                 this._tabAttrModified(aOurTab);
  2223                 if (aOurTab.selected)
  2224                   this.mIsBusy = true;
  2227               this._swapBrowserDocShells(aOurTab, otherBrowser);
  2230             // Handle findbar data (if any)
  2231             let otherFindBar = aOtherTab._findBar;
  2232             if (otherFindBar &&
  2233                 otherFindBar.findMode == otherFindBar.FIND_NORMAL) {
  2234               let ourFindBar = this.getFindBar(aOurTab);
  2235               ourFindBar._findField.value = otherFindBar._findField.value;
  2236               if (!otherFindBar.hidden)
  2237                 ourFindBar.onFindCommand();
  2240             // Finish tearing down the tab that's going away.
  2241             remoteBrowser._endRemoveTab(aOtherTab);
  2243             if (isBusy)
  2244               this.setTabTitleLoading(aOurTab);
  2245             else
  2246               this.setTabTitle(aOurTab);
  2248             // If the tab was already selected (this happpens in the scenario
  2249             // of replaceTabWithWindow), notify onLocationChange, etc.
  2250             if (aOurTab.selected)
  2251               this.updateCurrentBrowser(true);
  2252           ]]>
  2253         </body>
  2254       </method>
  2256       <method name="_swapBrowserDocShells">
  2257         <parameter name="aOurTab"/>
  2258         <parameter name="aOtherBrowser"/>
  2259         <body>
  2260           <![CDATA[
  2261             // Unhook our progress listener
  2262             let index = aOurTab._tPos;
  2263             const filter = this.mTabFilters[index];
  2264             let tabListener = this.mTabListeners[index];
  2265             let ourBrowser = this.getBrowserForTab(aOurTab);
  2266             ourBrowser.webProgress.removeProgressListener(filter);
  2267             filter.removeProgressListener(tabListener);
  2269             // Make sure to unregister any open URIs.
  2270             this._swapRegisteredOpenURIs(ourBrowser, aOtherBrowser);
  2272             // Give others a chance to swap state.
  2273             let event = new CustomEvent("SwapDocShells", {"detail": aOtherBrowser});
  2274             ourBrowser.dispatchEvent(event);
  2276             // Swap the docshells
  2277             ourBrowser.swapDocShells(aOtherBrowser);
  2279             // Restore the progress listener
  2280             this.mTabListeners[index] = tabListener =
  2281               this.mTabProgressListener(aOurTab, ourBrowser, false);
  2283             const notifyAll = Ci.nsIWebProgress.NOTIFY_ALL;
  2284             filter.addProgressListener(tabListener, notifyAll);
  2285             ourBrowser.webProgress.addProgressListener(filter, notifyAll);
  2286           ]]>
  2287         </body>
  2288       </method>
  2290       <method name="_swapRegisteredOpenURIs">
  2291         <parameter name="aOurBrowser"/>
  2292         <parameter name="aOtherBrowser"/>
  2293         <body>
  2294           <![CDATA[
  2295             // If the current URI is registered as open remove it from the list.
  2296             if (aOurBrowser.registeredOpenURI) {
  2297               this._placesAutocomplete.unregisterOpenPage(aOurBrowser.registeredOpenURI);
  2298               this._unifiedComplete.unregisterOpenPage(aOurBrowser.registeredOpenURI);
  2299               delete aOurBrowser.registeredOpenURI;
  2302             // If the other/new URI is registered as open then copy it over.
  2303             if (aOtherBrowser.registeredOpenURI) {
  2304               aOurBrowser.registeredOpenURI = aOtherBrowser.registeredOpenURI;
  2305               delete aOtherBrowser.registeredOpenURI;
  2307           ]]>
  2308         </body>
  2309       </method>
  2311       <method name="reloadAllTabs">
  2312         <body>
  2313           <![CDATA[
  2314             let tabs = this.visibleTabs;
  2315             let l = tabs.length;
  2316             for (var i = 0; i < l; i++) {
  2317               try {
  2318                 this.getBrowserForTab(tabs[i]).reload();
  2319               } catch (e) {
  2320                 // ignore failure to reload so others will be reloaded
  2323           ]]>
  2324         </body>
  2325       </method>
  2327       <method name="reloadTab">
  2328         <parameter name="aTab"/>
  2329         <body>
  2330           <![CDATA[
  2331             this.getBrowserForTab(aTab).reload();
  2332           ]]>
  2333         </body>
  2334       </method>
  2336       <method name="addProgressListener">
  2337         <parameter name="aListener"/>
  2338         <body>
  2339           <![CDATA[
  2340             if (arguments.length != 1) {
  2341               Components.utils.reportError("gBrowser.addProgressListener was " +
  2342                                            "called with a second argument, " +
  2343                                            "which is not supported. See bug " +
  2344                                            "608628. Call stack: " + new Error().stack);
  2347             this.mProgressListeners.push(aListener);
  2348           ]]>
  2349         </body>
  2350       </method>
  2352       <method name="removeProgressListener">
  2353         <parameter name="aListener"/>
  2354         <body>
  2355           <![CDATA[
  2356             this.mProgressListeners =
  2357               this.mProgressListeners.filter(function (l) l != aListener);
  2358          ]]>
  2359         </body>
  2360       </method>
  2362       <method name="addTabsProgressListener">
  2363         <parameter name="aListener"/>
  2364         <body>
  2365           this.mTabsProgressListeners.push(aListener);
  2366         </body>
  2367       </method>
  2369       <method name="removeTabsProgressListener">
  2370         <parameter name="aListener"/>
  2371         <body>
  2372         <![CDATA[
  2373           this.mTabsProgressListeners =
  2374             this.mTabsProgressListeners.filter(function (l) l != aListener);
  2375         ]]>
  2376         </body>
  2377       </method>
  2379       <method name="getBrowserForTab">
  2380         <parameter name="aTab"/>
  2381         <body>
  2382         <![CDATA[
  2383           return aTab.linkedBrowser;
  2384         ]]>
  2385         </body>
  2386       </method>
  2388       <method name="showOnlyTheseTabs">
  2389         <parameter name="aTabs"/>
  2390         <body>
  2391         <![CDATA[
  2392           Array.forEach(this.tabs, function(tab) {
  2393             if (aTabs.indexOf(tab) == -1)
  2394               this.hideTab(tab);
  2395             else
  2396               this.showTab(tab);
  2397           }, this);
  2399           this.tabContainer._handleTabSelect(false);
  2400         ]]>
  2401         </body>
  2402       </method>
  2404       <method name="showTab">
  2405         <parameter name="aTab"/>
  2406         <body>
  2407         <![CDATA[
  2408           if (aTab.hidden) {
  2409             aTab.removeAttribute("hidden");
  2410             this._visibleTabs = null; // invalidate cache
  2412             this.tabContainer.adjustTabstrip();
  2414             this.tabContainer._setPositionalAttributes();
  2416             let event = document.createEvent("Events");
  2417             event.initEvent("TabShow", true, false);
  2418             aTab.dispatchEvent(event);
  2420         ]]>
  2421         </body>
  2422       </method>
  2424       <method name="hideTab">
  2425         <parameter name="aTab"/>
  2426         <body>
  2427         <![CDATA[
  2428           if (!aTab.hidden && !aTab.pinned && !aTab.selected &&
  2429               !aTab.closing) {
  2430             aTab.setAttribute("hidden", "true");
  2431             this._visibleTabs = null; // invalidate cache
  2433             this.tabContainer.adjustTabstrip();
  2435             this.tabContainer._setPositionalAttributes();
  2437             let event = document.createEvent("Events");
  2438             event.initEvent("TabHide", true, false);
  2439             aTab.dispatchEvent(event);
  2441         ]]>
  2442         </body>
  2443       </method>
  2445       <method name="selectTabAtIndex">
  2446         <parameter name="aIndex"/>
  2447         <parameter name="aEvent"/>
  2448         <body>
  2449         <![CDATA[
  2450           let tabs = this.visibleTabs;
  2452           // count backwards for aIndex < 0
  2453           if (aIndex < 0)
  2454             aIndex += tabs.length;
  2456           if (aIndex >= 0 && aIndex < tabs.length)
  2457             this.selectedTab = tabs[aIndex];
  2459           if (aEvent) {
  2460             aEvent.preventDefault();
  2461             aEvent.stopPropagation();
  2463         ]]>
  2464         </body>
  2465       </method>
  2467       <property name="selectedTab">
  2468         <getter>
  2469           return this.mCurrentTab;
  2470         </getter>
  2471         <setter>
  2472           <![CDATA[
  2473           // Update the tab
  2474           this.mTabBox.selectedTab = val;
  2475           return val;
  2476           ]]>
  2477         </setter>
  2478       </property>
  2480       <property name="selectedBrowser"
  2481                 onget="return this.mCurrentBrowser;"
  2482                 readonly="true"/>
  2484       <property name="browsers" readonly="true">
  2485        <getter>
  2486           <![CDATA[
  2487             return this._browsers ||
  2488                    (this._browsers = Array.map(this.tabs, function (tab) tab.linkedBrowser));
  2489           ]]>
  2490         </getter>
  2491       </property>
  2492       <field name="_browsers">null</field>
  2494       <!-- Moves a tab to a new browser window, unless it's already the only tab
  2495            in the current window, in which case this will do nothing. -->
  2496       <method name="replaceTabWithWindow">
  2497         <parameter name="aTab"/>
  2498         <parameter name="aOptions"/>
  2499         <body>
  2500           <![CDATA[
  2501             if (this.tabs.length == 1)
  2502               return null;
  2504             let event = new CustomEvent("TabBecomingWindow", {
  2505               bubbles: true,
  2506               cancelable: true
  2507             });
  2508             aTab.dispatchEvent(event);
  2509             if (event.defaultPrevented) {
  2510               return null;
  2513             var options = "chrome,dialog=no,all";
  2514             for (var name in aOptions)
  2515               options += "," + name + "=" + aOptions[name];
  2517             // tell a new window to take the "dropped" tab
  2518             return window.openDialog(getBrowserURL(), "_blank", options, aTab);
  2519           ]]>
  2520         </body>
  2521       </method>
  2523       <method name="moveTabTo">
  2524         <parameter name="aTab"/>
  2525         <parameter name="aIndex"/>
  2526         <body>
  2527         <![CDATA[
  2528           var oldPosition = aTab._tPos;
  2529           if (oldPosition == aIndex)
  2530             return;
  2532           // Don't allow mixing pinned and unpinned tabs.
  2533           if (aTab.pinned)
  2534             aIndex = Math.min(aIndex, this._numPinnedTabs - 1);
  2535           else
  2536             aIndex = Math.max(aIndex, this._numPinnedTabs);
  2537           if (oldPosition == aIndex)
  2538             return;
  2540           this._lastRelatedTab = null;
  2542           this.mTabFilters.splice(aIndex, 0, this.mTabFilters.splice(aTab._tPos, 1)[0]);
  2543           this.mTabListeners.splice(aIndex, 0, this.mTabListeners.splice(aTab._tPos, 1)[0]);
  2545           let wasFocused = (document.activeElement == this.mCurrentTab);
  2547           aIndex = aIndex < aTab._tPos ? aIndex: aIndex+1;
  2548           this.mCurrentTab._selected = false;
  2550           // invalidate caches
  2551           this._browsers = null;
  2552           this._visibleTabs = null;
  2554           // use .item() instead of [] because dragging to the end of the strip goes out of
  2555           // bounds: .item() returns null (so it acts like appendChild), but [] throws
  2556           this.tabContainer.insertBefore(aTab, this.tabs.item(aIndex));
  2558           for (let i = 0; i < this.tabs.length; i++) {
  2559             this.tabs[i]._tPos = i;
  2560             this.tabs[i]._selected = false;
  2562           this.mCurrentTab._selected = true;
  2564           if (wasFocused)
  2565             this.mCurrentTab.focus();
  2567           this.tabContainer._handleTabSelect(false);
  2569           if (aTab.pinned)
  2570             this.tabContainer._positionPinnedTabs();
  2572           this.tabContainer._setPositionalAttributes();
  2574           var evt = document.createEvent("UIEvents");
  2575           evt.initUIEvent("TabMove", true, false, window, oldPosition);
  2576           aTab.dispatchEvent(evt);
  2577         ]]>
  2578         </body>
  2579       </method>
  2581       <method name="moveTabForward">
  2582         <body>
  2583           <![CDATA[
  2584             let nextTab = this.mCurrentTab.nextSibling;
  2585             while (nextTab && nextTab.hidden)
  2586               nextTab = nextTab.nextSibling;
  2588             if (nextTab)
  2589               this.moveTabTo(this.mCurrentTab, nextTab._tPos);
  2590             else if (this.arrowKeysShouldWrap)
  2591               this.moveTabToStart();
  2592           ]]>
  2593         </body>
  2594       </method>
  2596       <method name="moveTabBackward">
  2597         <body>
  2598           <![CDATA[
  2599             let previousTab = this.mCurrentTab.previousSibling;
  2600             while (previousTab && previousTab.hidden)
  2601               previousTab = previousTab.previousSibling;
  2603             if (previousTab)
  2604               this.moveTabTo(this.mCurrentTab, previousTab._tPos);
  2605             else if (this.arrowKeysShouldWrap)
  2606               this.moveTabToEnd();
  2607           ]]>
  2608         </body>
  2609       </method>
  2611       <method name="moveTabToStart">
  2612         <body>
  2613           <![CDATA[
  2614             var tabPos = this.mCurrentTab._tPos;
  2615             if (tabPos > 0)
  2616               this.moveTabTo(this.mCurrentTab, 0);
  2617           ]]>
  2618         </body>
  2619       </method>
  2621       <method name="moveTabToEnd">
  2622         <body>
  2623           <![CDATA[
  2624             var tabPos = this.mCurrentTab._tPos;
  2625             if (tabPos < this.browsers.length - 1)
  2626               this.moveTabTo(this.mCurrentTab, this.browsers.length - 1);
  2627           ]]>
  2628         </body>
  2629       </method>
  2631       <method name="moveTabOver">
  2632         <parameter name="aEvent"/>
  2633         <body>
  2634           <![CDATA[
  2635             var direction = window.getComputedStyle(this.parentNode, null).direction;
  2636             if ((direction == "ltr" && aEvent.keyCode == KeyEvent.DOM_VK_RIGHT) ||
  2637                 (direction == "rtl" && aEvent.keyCode == KeyEvent.DOM_VK_LEFT))
  2638               this.moveTabForward();
  2639             else
  2640               this.moveTabBackward();
  2641           ]]>
  2642         </body>
  2643       </method>
  2645       <method name="duplicateTab">
  2646         <parameter name="aTab"/><!-- can be from a different window as well -->
  2647         <body>
  2648           <![CDATA[
  2649             return SessionStore.duplicateTab(window, aTab);
  2650           ]]>
  2651         </body>
  2652       </method>
  2654       <!-- BEGIN FORWARDED BROWSER PROPERTIES.  IF YOU ADD A PROPERTY TO THE BROWSER ELEMENT
  2655            MAKE SURE TO ADD IT HERE AS WELL. -->
  2656       <property name="canGoBack"
  2657                 onget="return this.mCurrentBrowser.canGoBack;"
  2658                 readonly="true"/>
  2660       <property name="canGoForward"
  2661                 onget="return this.mCurrentBrowser.canGoForward;"
  2662                 readonly="true"/>
  2664       <method name="goBack">
  2665         <body>
  2666           <![CDATA[
  2667             return this.mCurrentBrowser.goBack();
  2668           ]]>
  2669         </body>
  2670       </method>
  2672       <method name="goForward">
  2673         <body>
  2674           <![CDATA[
  2675             return this.mCurrentBrowser.goForward();
  2676           ]]>
  2677         </body>
  2678       </method>
  2680       <method name="reload">
  2681         <body>
  2682           <![CDATA[
  2683             return this.mCurrentBrowser.reload();
  2684           ]]>
  2685         </body>
  2686       </method>
  2688       <method name="reloadWithFlags">
  2689         <parameter name="aFlags"/>
  2690         <body>
  2691           <![CDATA[
  2692             return this.mCurrentBrowser.reloadWithFlags(aFlags);
  2693           ]]>
  2694         </body>
  2695       </method>
  2697       <method name="stop">
  2698         <body>
  2699           <![CDATA[
  2700             return this.mCurrentBrowser.stop();
  2701           ]]>
  2702         </body>
  2703       </method>
  2705       <!-- throws exception for unknown schemes -->
  2706       <method name="loadURI">
  2707         <parameter name="aURI"/>
  2708         <parameter name="aReferrerURI"/>
  2709         <parameter name="aCharset"/>
  2710         <body>
  2711           <![CDATA[
  2712 #ifdef MAKE_E10S_WORK
  2713             this.updateBrowserRemoteness(this.mCurrentBrowser, aURI);
  2714             try {
  2715 #endif
  2716             return this.mCurrentBrowser.loadURI(aURI, aReferrerURI, aCharset);
  2717 #ifdef MAKE_E10S_WORK
  2718             } catch (e) {
  2719               let url = this.mCurrentBrowser.currentURI.spec;
  2720               this.updateBrowserRemoteness(this.mCurrentBrowser, url);
  2721               throw e;
  2723 #endif
  2724           ]]>
  2725         </body>
  2726       </method>
  2728       <!-- throws exception for unknown schemes -->
  2729       <method name="loadURIWithFlags">
  2730         <parameter name="aURI"/>
  2731         <parameter name="aFlags"/>
  2732         <parameter name="aReferrerURI"/>
  2733         <parameter name="aCharset"/>
  2734         <parameter name="aPostData"/>
  2735         <body>
  2736           <![CDATA[
  2737 #ifdef MAKE_E10S_WORK
  2738             this.updateBrowserRemoteness(this.mCurrentBrowser, aURI);
  2739             try {
  2740 #endif
  2741             return this.mCurrentBrowser.loadURIWithFlags(aURI, aFlags, aReferrerURI, aCharset, aPostData);
  2742 #ifdef MAKE_E10S_WORK
  2743             } catch (e) {
  2744               let url = this.mCurrentBrowser.currentURI.spec;
  2745               this.updateBrowserRemoteness(this.mCurrentBrowser, url);
  2746               throw e;
  2748 #endif
  2749           ]]>
  2750         </body>
  2751       </method>
  2753       <method name="goHome">
  2754         <body>
  2755           <![CDATA[
  2756             return this.mCurrentBrowser.goHome();
  2757           ]]>
  2758         </body>
  2759       </method>
  2761       <property name="homePage">
  2762         <getter>
  2763           <![CDATA[
  2764             return this.mCurrentBrowser.homePage;
  2765           ]]>
  2766         </getter>
  2767         <setter>
  2768           <![CDATA[
  2769             this.mCurrentBrowser.homePage = val;
  2770             return val;
  2771           ]]>
  2772         </setter>
  2773       </property>
  2775       <method name="gotoIndex">
  2776         <parameter name="aIndex"/>
  2777         <body>
  2778           <![CDATA[
  2779             return this.mCurrentBrowser.gotoIndex(aIndex);
  2780           ]]>
  2781         </body>
  2782       </method>
  2784       <method name="attachFormFill">
  2785         <body><![CDATA[
  2786           for (var i = 0; i < this.mPanelContainer.childNodes.length; ++i) {
  2787             var cb = this.getBrowserAtIndex(i);
  2788             cb.attachFormFill();
  2790         ]]></body>
  2791       </method>
  2793       <method name="detachFormFill">
  2794         <body><![CDATA[
  2795           for (var i = 0; i < this.mPanelContainer.childNodes.length; ++i) {
  2796             var cb = this.getBrowserAtIndex(i);
  2797             cb.detachFormFill();
  2799         ]]></body>
  2800       </method>
  2802       <property name="currentURI"
  2803                 onget="return this.mCurrentBrowser.currentURI;"
  2804                 readonly="true"/>
  2806       <property name="finder"
  2807                 onget="return this.mCurrentBrowser.finder"
  2808                 readonly="true"/>
  2810       <property name="docShell"
  2811                 onget="return this.mCurrentBrowser.docShell"
  2812                 readonly="true"/>
  2814       <property name="webNavigation"
  2815                 onget="return this.mCurrentBrowser.webNavigation"
  2816                 readonly="true"/>
  2818       <property name="webBrowserFind"
  2819                 readonly="true"
  2820                 onget="return this.mCurrentBrowser.webBrowserFind"/>
  2822       <property name="webProgress"
  2823                 readonly="true"
  2824                 onget="return this.mCurrentBrowser.webProgress"/>
  2826       <property name="contentWindow"
  2827                 readonly="true"
  2828                 onget="return this.mCurrentBrowser.contentWindow"/>
  2830       <property name="sessionHistory"
  2831                 onget="return this.mCurrentBrowser.sessionHistory;"
  2832                 readonly="true"/>
  2834       <property name="markupDocumentViewer"
  2835                 onget="return this.mCurrentBrowser.markupDocumentViewer;"
  2836                 readonly="true"/>
  2838       <property name="contentViewerEdit"
  2839                 onget="return this.mCurrentBrowser.contentViewerEdit;"
  2840                 readonly="true"/>
  2842       <property name="contentViewerFile"
  2843                 onget="return this.mCurrentBrowser.contentViewerFile;"
  2844                 readonly="true"/>
  2846       <property name="contentDocument"
  2847                 onget="return this.mCurrentBrowser.contentDocument;"
  2848                 readonly="true"/>
  2850       <property name="contentTitle"
  2851                 onget="return this.mCurrentBrowser.contentTitle;"
  2852                 readonly="true"/>
  2854       <property name="contentPrincipal"
  2855                 onget="return this.mCurrentBrowser.contentPrincipal;"
  2856                 readonly="true"/>
  2858       <property name="securityUI"
  2859                 onget="return this.mCurrentBrowser.securityUI;"
  2860                 readonly="true"/>
  2862       <property name="fullZoom"
  2863                 onget="return this.mCurrentBrowser.fullZoom;"
  2864                 onset="this.mCurrentBrowser.fullZoom = val;"/>
  2866       <property name="textZoom"
  2867                 onget="return this.mCurrentBrowser.textZoom;"
  2868                 onset="this.mCurrentBrowser.textZoom = val;"/>
  2870       <property name="isSyntheticDocument"
  2871                 onget="return this.mCurrentBrowser.isSyntheticDocument;"
  2872                 readonly="true"/>
  2874       <method name="_handleKeyEvent">
  2875         <parameter name="aEvent"/>
  2876         <body><![CDATA[
  2877           if (!aEvent.isTrusted) {
  2878             // Don't let untrusted events mess with tabs.
  2879             return;
  2882           if (aEvent.altKey)
  2883             return;
  2885           if (aEvent.ctrlKey && aEvent.shiftKey && !aEvent.metaKey) {
  2886             switch (aEvent.keyCode) {
  2887               case aEvent.DOM_VK_PAGE_UP:
  2888                 this.moveTabBackward();
  2889                 aEvent.stopPropagation();
  2890                 aEvent.preventDefault();
  2891                 return;
  2892               case aEvent.DOM_VK_PAGE_DOWN:
  2893                 this.moveTabForward();
  2894                 aEvent.stopPropagation();
  2895                 aEvent.preventDefault();
  2896                 return;
  2900           // We need to take care of FAYT-watching as long as the findbar
  2901           // isn't initialized.  The checks on aEvent are copied from
  2902           // _shouldFastFind (see findbar.xml).
  2903           if (!gFindBarInitialized &&
  2904               !(aEvent.ctrlKey || aEvent.metaKey) &&
  2905               !aEvent.defaultPrevented) {
  2906             let charCode = aEvent.charCode;
  2907             if (charCode) {
  2908               let char = String.fromCharCode(charCode);
  2909               if (char == "'" || char == "/" ||
  2910                   Services.prefs.getBoolPref("accessibility.typeaheadfind")) {
  2911                 gFindBar._onBrowserKeypress(aEvent);
  2912                 return;
  2917 #ifdef XP_MACOSX
  2918           if (!aEvent.metaKey)
  2919             return;
  2921           var offset = 1;
  2922           switch (aEvent.charCode) {
  2923             case '}'.charCodeAt(0):
  2924               offset = -1;
  2925             case '{'.charCodeAt(0):
  2926               if (window.getComputedStyle(this, null).direction == "ltr")
  2927                 offset *= -1;
  2928               this.tabContainer.advanceSelectedTab(offset, true);
  2929               aEvent.stopPropagation();
  2930               aEvent.preventDefault();
  2932 #else
  2933           if (aEvent.ctrlKey && !aEvent.shiftKey && !aEvent.metaKey &&
  2934               aEvent.keyCode == KeyEvent.DOM_VK_F4 &&
  2935               !this.mCurrentTab.pinned) {
  2936             this.removeCurrentTab({animate: true});
  2937             aEvent.stopPropagation();
  2938             aEvent.preventDefault();
  2940 #endif
  2941         ]]></body>
  2942       </method>
  2944       <property name="userTypedClear"
  2945                 onget="return this.mCurrentBrowser.userTypedClear;"
  2946                 onset="return this.mCurrentBrowser.userTypedClear = val;"/>
  2948       <property name="userTypedValue"
  2949                 onget="return this.mCurrentBrowser.userTypedValue;"
  2950                 onset="return this.mCurrentBrowser.userTypedValue = val;"/>
  2952       <method name="createTooltip">
  2953         <parameter name="event"/>
  2954         <body><![CDATA[
  2955           event.stopPropagation();
  2956           var tab = document.tooltipNode;
  2957           if (tab.localName != "tab") {
  2958             event.preventDefault();
  2959             return;
  2961           event.target.setAttribute("label", tab.mOverCloseButton ?
  2962                                              tab.getAttribute("closetabtext") :
  2963                                              tab.getAttribute("label"));
  2964         ]]></body>
  2965       </method>
  2967       <method name="handleEvent">
  2968         <parameter name="aEvent"/>
  2969         <body><![CDATA[
  2970           switch (aEvent.type) {
  2971             case "keypress":
  2972               this._handleKeyEvent(aEvent);
  2973               break;
  2974             case "sizemodechange":
  2975               if (aEvent.target == window) {
  2976                 this.mCurrentBrowser.docShellIsActive =
  2977                   (window.windowState != window.STATE_MINIMIZED);
  2979               break;
  2981         ]]></body>
  2982       </method>
  2984       <method name="receiveMessage">
  2985         <parameter name="aMessage"/>
  2986         <body><![CDATA[
  2987           let json = aMessage.json;
  2988           let browser = aMessage.target;
  2990           switch (aMessage.name) {
  2991             case "DOMTitleChanged": {
  2992               let tab = this._getTabForBrowser(browser);
  2993               if (!tab || tab.hasAttribute("pending"))
  2994                 return;
  2995               let titleChanged = this.setTabTitle(tab);
  2996               if (titleChanged && !tab.selected && !tab.hasAttribute("busy"))
  2997                 tab.setAttribute("titlechanged", "true");
  2998               break;
  3000             case "DOMWindowClose": {
  3001               if (this.tabs.length == 1) {
  3002                 window.close();
  3003                 return;
  3006               let tab = this._getTabForBrowser(browser);
  3007               if (tab) {
  3008                 this.removeTab(tab);
  3010               break;
  3012             case "contextmenu": {
  3013               gContextMenuContentData = { event: aMessage.objects.event,
  3014                                           browser: browser };
  3015               let popup = browser.ownerDocument.getElementById("contentAreaContextMenu");
  3016               let event = gContextMenuContentData.event;
  3017               let pos = browser.mapScreenCoordinatesFromContent(event.screenX, event.screenY);
  3018               popup.openPopupAtScreen(pos.x, pos.y, true);
  3019               break;
  3021             case "DOMWebNotificationClicked": {
  3022               let tab = this._getTabForBrowser(browser);
  3023               if (!tab)
  3024                 return;
  3025               this.selectedTab = tab;
  3026               window.focus();
  3027               break;
  3030         ]]></body>
  3031       </method>
  3033       <constructor>
  3034         <![CDATA[
  3035           let browserStack = document.getAnonymousElementByAttribute(this, "anonid", "browserStack");
  3036           this.mCurrentBrowser = document.getAnonymousElementByAttribute(this, "anonid", "initialBrowser");
  3038           this.mCurrentTab = this.tabContainer.firstChild;
  3039           document.addEventListener("keypress", this, false);
  3040           window.addEventListener("sizemodechange", this, false);
  3042           var uniqueId = this._generateUniquePanelID();
  3043           this.mPanelContainer.childNodes[0].id = uniqueId;
  3044           this.mCurrentTab.linkedPanel = uniqueId;
  3045           this.mCurrentTab._tPos = 0;
  3046           this.mCurrentTab._fullyOpen = true;
  3047           this.mCurrentTab.linkedBrowser = this.mCurrentBrowser;
  3049           // set up the shared autoscroll popup
  3050           this._autoScrollPopup = this.mCurrentBrowser._createAutoScrollPopup();
  3051           this._autoScrollPopup.id = "autoscroller";
  3052           this.appendChild(this._autoScrollPopup);
  3053           this.mCurrentBrowser.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
  3054           this.mCurrentBrowser.droppedLinkHandler = handleDroppedLink;
  3055           this.updateWindowResizers();
  3057           // Hook up the event listeners to the first browser
  3058           var tabListener = this.mTabProgressListener(this.mCurrentTab, this.mCurrentBrowser, true);
  3059           const nsIWebProgress = Components.interfaces.nsIWebProgress;
  3060           const filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
  3061                                    .createInstance(nsIWebProgress);
  3062           filter.addProgressListener(tabListener, nsIWebProgress.NOTIFY_ALL);
  3063           this.mTabListeners[0] = tabListener;
  3064           this.mTabFilters[0] = filter;
  3065           this.webProgress.addProgressListener(filter, nsIWebProgress.NOTIFY_ALL);
  3067           this.style.backgroundColor =
  3068             Services.prefs.getBoolPref("browser.display.use_system_colors") ?
  3069               "-moz-default-background-color" :
  3070               Services.prefs.getCharPref("browser.display.background_color");
  3072           let remote = window.QueryInterface(Ci.nsIInterfaceRequestor)
  3073             .getInterface(Ci.nsIWebNavigation)
  3074             .QueryInterface(Ci.nsILoadContext)
  3075             .useRemoteTabs;
  3076           if (remote) {
  3077             messageManager.addMessageListener("DOMTitleChanged", this);
  3078             messageManager.addMessageListener("DOMWindowClose", this);
  3079             messageManager.addMessageListener("contextmenu", this);
  3081           messageManager.addMessageListener("DOMWebNotificationClicked", this);
  3082         ]]>
  3083       </constructor>
  3085       <method name="_generateUniquePanelID">
  3086         <body><![CDATA[
  3087           if (!this._uniquePanelIDCounter) {
  3088             this._uniquePanelIDCounter = 0;
  3091           let outerID = window.QueryInterface(Ci.nsIInterfaceRequestor)
  3092                               .getInterface(Ci.nsIDOMWindowUtils)
  3093                               .outerWindowID;
  3095           // We want panel IDs to be globally unique, that's why we include the
  3096           // window ID. We switched to a monotonic counter as Date.now() lead
  3097           // to random failures because of colliding IDs.
  3098           return "panel-" + outerID + "-" + (++this._uniquePanelIDCounter);
  3099         ]]></body>
  3100       </method>
  3102       <destructor>
  3103         <![CDATA[
  3104           for (var i = 0; i < this.mTabListeners.length; ++i) {
  3105             let browser = this.getBrowserAtIndex(i);
  3106             if (browser.registeredOpenURI) {
  3107               this._placesAutocomplete.unregisterOpenPage(browser.registeredOpenURI);
  3108               this._unifiedComplete.unregisterOpenPage(browser.registeredOpenURI);
  3109               delete browser.registeredOpenURI;
  3111             browser.webProgress.removeProgressListener(this.mTabFilters[i]);
  3112             this.mTabFilters[i].removeProgressListener(this.mTabListeners[i]);
  3113             this.mTabFilters[i] = null;
  3114             this.mTabListeners[i].destroy();
  3115             this.mTabListeners[i] = null;
  3117           document.removeEventListener("keypress", this, false);
  3118           window.removeEventListener("sizemodechange", this, false);
  3120           if (gMultiProcessBrowser) {
  3121             messageManager.removeMessageListener("DOMTitleChanged", this);
  3122             messageManager.removeMessageListener("contextmenu", this);
  3124         ]]>
  3125       </destructor>
  3127       <!-- Deprecated stuff, implemented for backwards compatibility. -->
  3128       <method name="enterTabbedMode">
  3129         <body>
  3130           Application.console.log("enterTabbedMode is an obsolete method and " +
  3131                                   "will be removed in a future release.");
  3132         </body>
  3133       </method>
  3134       <field name="mTabbedMode" readonly="true">true</field>
  3135       <method name="setStripVisibilityTo">
  3136         <parameter name="aShow"/>
  3137         <body>
  3138           this.tabContainer.visible = aShow;
  3139         </body>
  3140       </method>
  3141       <method name="getStripVisibility">
  3142         <body>
  3143           return this.tabContainer.visible;
  3144         </body>
  3145       </method>
  3146       <property name="mContextTab" readonly="true"
  3147                 onget="return TabContextMenu.contextTab;"/>
  3148       <property name="mPrefs" readonly="true"
  3149                 onget="return Services.prefs;"/>
  3150       <property name="mTabContainer" readonly="true"
  3151                 onget="return this.tabContainer;"/>
  3152       <property name="mTabs" readonly="true"
  3153                 onget="return this.tabs;"/>
  3154       <!--
  3155         - Compatibility hack: several extensions depend on this property to
  3156         - access the tab context menu or tab container, so keep that working for
  3157         - now. Ideally we can remove this once extensions are using
  3158         - tabbrowser.tabContextMenu and tabbrowser.tabContainer directly.
  3159         -->
  3160       <property name="mStrip" readonly="true">
  3161         <getter>
  3162         <![CDATA[
  3163           return ({
  3164             self: this,
  3165             childNodes: [null, this.tabContextMenu, this.tabContainer],
  3166             firstChild: { nextSibling: this.tabContextMenu },
  3167             getElementsByAttribute: function (attr, attrValue) {
  3168               if (attr == "anonid" && attrValue == "tabContextMenu")
  3169                 return [this.self.tabContextMenu];
  3170               return [];
  3171             },
  3172             // Also support adding event listeners (forward to the tab container)
  3173             addEventListener: function (a,b,c) { this.self.tabContainer.addEventListener(a,b,c); },
  3174             removeEventListener: function (a,b,c) { this.self.tabContainer.removeEventListener(a,b,c); }
  3175           });
  3176         ]]>
  3177         </getter>
  3178       </property>
  3179     </implementation>
  3181     <handlers>
  3182       <handler event="DOMWindowClose" phase="capturing">
  3183         <![CDATA[
  3184           if (!event.isTrusted)
  3185             return;
  3187           if (this.tabs.length == 1)
  3188             return;
  3190           var tab = this._getTabForContentWindow(event.target);
  3191           if (tab) {
  3192             this.removeTab(tab);
  3193             event.preventDefault();
  3195         ]]>
  3196       </handler>
  3197       <handler event="DOMWillOpenModalDialog" phase="capturing">
  3198         <![CDATA[
  3199           if (!event.isTrusted)
  3200             return;
  3202           // We're about to open a modal dialog, make sure the opening
  3203           // tab is brought to the front.
  3204           // If this is a same-process modal dialog, then we're given its DOM
  3205           // window as the event's target. For remote dialogs, we're given the
  3206           // browser, but that's in the originalTarget.
  3207           // XXX Why originalTarget for the browser?
  3208           this.selectedTab = (event.target instanceof Window) ?
  3209                                this._getTabForContentWindow(event.target.top) :
  3210                                this._getTabForBrowser(event.originalTarget);
  3211         ]]>
  3212       </handler>
  3213       <handler event="DOMTitleChanged">
  3214         <![CDATA[
  3215           if (!event.isTrusted)
  3216             return;
  3218           var contentWin = event.target.defaultView;
  3219           if (contentWin != contentWin.top)
  3220             return;
  3222           var tab = this._getTabForContentWindow(contentWin);
  3223           if (tab.hasAttribute("pending"))
  3224             return;
  3226           var titleChanged = this.setTabTitle(tab);
  3227           if (titleChanged && !tab.selected && !tab.hasAttribute("busy"))
  3228             tab.setAttribute("titlechanged", "true");
  3229         ]]>
  3230       </handler>
  3231       <handler event="oop-browser-crashed">
  3232         <![CDATA[
  3233           if (!event.isTrusted)
  3234             return;
  3236           let browser = event.originalTarget;
  3237           let title = browser.contentTitle;
  3238           let uri = browser.currentURI;
  3239           let icon = browser.mIconURL;
  3241           this.updateBrowserRemoteness(browser, "about:tabcrashed");
  3243           browser.setAttribute("crashedPageTitle", title);
  3244           browser.docShell.displayLoadError(Cr.NS_ERROR_CONTENT_CRASHED, uri, null);
  3245           browser.removeAttribute("crashedPageTitle");
  3246           let tab = this._getTabForBrowser(browser);
  3247           this.setIcon(tab, icon);
  3248         ]]>
  3249       </handler>
  3250     </handlers>
  3251   </binding>
  3253   <binding id="tabbrowser-tabbox"
  3254            extends="chrome://global/content/bindings/tabbox.xml#tabbox">
  3255     <implementation>
  3256       <property name="tabs" readonly="true"
  3257                 onget="return document.getBindingParent(this).tabContainer;"/>
  3258     </implementation>
  3259   </binding>
  3261   <binding id="tabbrowser-arrowscrollbox" extends="chrome://global/content/bindings/scrollbox.xml#arrowscrollbox-clicktoscroll">
  3262     <implementation>
  3263       <!-- Override scrollbox.xml method, since our scrollbox's children are
  3264            inherited from the binding parent -->
  3265       <method name="_getScrollableElements">
  3266         <body><![CDATA[
  3267           return Array.filter(document.getBindingParent(this).childNodes,
  3268                               this._canScrollToElement, this);
  3269         ]]></body>
  3270       </method>
  3271       <method name="_canScrollToElement">
  3272         <parameter name="tab"/>
  3273         <body><![CDATA[
  3274           return !tab.pinned && !tab.hidden;
  3275         ]]></body>
  3276       </method>
  3277       <field name="_tabMarginLeft">null</field>
  3278       <field name="_tabMarginRight">null</field>
  3279       <method name="_calcTabMargins">
  3280         <parameter name="aTab"/>
  3281         <body><![CDATA[
  3282           if (this._tabMarginLeft === null || this._tabMarginRight === null) {
  3283             let tabMiddle = document.getAnonymousElementByAttribute(aTab, "class", "tab-background-middle");
  3284             let tabMiddleStyle = window.getComputedStyle(tabMiddle, null);
  3285             this._tabMarginLeft = parseFloat(tabMiddleStyle.marginLeft);
  3286             this._tabMarginRight = parseFloat(tabMiddleStyle.marginRight);
  3288         ]]></body>
  3289       </method>
  3290       <method name="_adjustElementStartAndEnd">
  3291         <parameter name="aTab"/>
  3292         <parameter name="tabStart"/>
  3293         <parameter name="tabEnd"/>
  3294         <body><![CDATA[
  3295           this._calcTabMargins(aTab);
  3296           if (this._tabMarginLeft < 0) {
  3297             tabStart = tabStart + this._tabMarginLeft;
  3299           if (this._tabMarginRight < 0) {
  3300             tabEnd = tabEnd - this._tabMarginRight;
  3302           return [tabStart, tabEnd];
  3303         ]]></body>
  3304       </method>
  3305     </implementation>
  3307     <handlers>
  3308       <handler event="underflow" phase="capturing"><![CDATA[
  3309         if (event.detail == 0)
  3310           return; // Ignore vertical events
  3312         var tabs = document.getBindingParent(this);
  3313         tabs.removeAttribute("overflow");
  3315         if (tabs._lastTabClosedByMouse)
  3316           tabs._expandSpacerBy(this._scrollButtonDown.clientWidth);
  3318         tabs.tabbrowser._removingTabs.forEach(tabs.tabbrowser.removeTab,
  3319                                               tabs.tabbrowser);
  3321         tabs._positionPinnedTabs();
  3322       ]]></handler>
  3323       <handler event="overflow"><![CDATA[
  3324         if (event.detail == 0)
  3325           return; // Ignore vertical events
  3326         var tabs = document.getBindingParent(this);
  3327         var numberOfTabs = tabs.tabbrowser.visibleTabs.length;
  3328         if (numberOfTabs == 1)
  3329           return;
  3331         tabs.setAttribute("overflow", "true");
  3332         tabs._positionPinnedTabs();
  3333         tabs._handleTabSelect(false);
  3334       ]]></handler>
  3335     </handlers>
  3336   </binding>
  3338   <binding id="tabbrowser-tabs"
  3339            extends="chrome://global/content/bindings/tabbox.xml#tabs">
  3340     <resources>
  3341       <stylesheet src="chrome://browser/content/tabbrowser.css"/>
  3342     </resources>
  3344     <content>
  3345       <xul:hbox align="end">
  3346         <xul:image class="tab-drop-indicator" anonid="tab-drop-indicator" collapsed="true"/>
  3347       </xul:hbox>
  3348       <xul:arrowscrollbox anonid="arrowscrollbox" orient="horizontal" flex="1"
  3349                           style="min-width: 1px;"
  3350 #ifndef XP_MACOSX
  3351                           clicktoscroll="true"
  3352 #endif
  3353                           class="tabbrowser-arrowscrollbox">
  3354 # This is a hack to circumvent bug 472020, otherwise the tabs show up on the
  3355 # right of the newtab button.
  3356         <children includes="tab"/>
  3357 # This is to ensure anything extensions put here will go before the newtab
  3358 # button, necessary due to the previous hack.
  3359         <children/>
  3360         <xul:toolbarbutton class="tabs-newtab-button"
  3361                            command="cmd_newNavigatorTab"
  3362                            onclick="checkForMiddleClick(this, event);"
  3363                            onmouseover="document.getBindingParent(this)._enterNewTab();"
  3364                            onmouseout="document.getBindingParent(this)._leaveNewTab();"
  3365                            tooltiptext="&newTabButton.tooltip;"/>
  3366         <xul:spacer class="closing-tabs-spacer" anonid="closing-tabs-spacer"
  3367                     style="width: 0;"/>
  3368       </xul:arrowscrollbox>
  3369     </content>
  3371     <implementation implements="nsIDOMEventListener">
  3372       <constructor>
  3373         <![CDATA[
  3374           this.mTabClipWidth = Services.prefs.getIntPref("browser.tabs.tabClipWidth");
  3376           var tab = this.firstChild;
  3377           tab.label = this.tabbrowser.mStringBundle.getString("tabs.emptyTabTitle");
  3378           tab.setAttribute("crop", "end");
  3379           tab.setAttribute("onerror", "this.removeAttribute('image');");
  3381           window.addEventListener("resize", this, false);
  3382           window.addEventListener("load", this, false);
  3384           try {
  3385             this._tabAnimationLoggingEnabled = Services.prefs.getBoolPref("browser.tabs.animationLogging.enabled");
  3386           } catch (ex) {
  3387             this._tabAnimationLoggingEnabled = false;
  3389           this._browserNewtabpageEnabled = Services.prefs.getBoolPref("browser.newtabpage.enabled");
  3390         ]]>
  3391       </constructor>
  3393       <field name="tabbrowser" readonly="true">
  3394         document.getElementById(this.getAttribute("tabbrowser"));
  3395       </field>
  3397       <field name="tabbox" readonly="true">
  3398         this.tabbrowser.mTabBox;
  3399       </field>
  3401       <field name="contextMenu" readonly="true">
  3402         document.getElementById("tabContextMenu");
  3403       </field>
  3405       <field name="mTabstripWidth">0</field>
  3407       <field name="mTabstrip">
  3408         document.getAnonymousElementByAttribute(this, "anonid", "arrowscrollbox");
  3409       </field>
  3411       <field name="_firstTab">null</field>
  3412       <field name="_lastTab">null</field>
  3413       <field name="_afterSelectedTab">null</field>
  3414       <field name="_beforeHoveredTab">null</field>
  3415       <field name="_afterHoveredTab">null</field>
  3416       <field name="_hoveredTab">null</field>
  3418       <property name="_isCustomizing" readonly="true">
  3419         <getter>
  3420           let root = document.documentElement;
  3421           return root.getAttribute("customizing") == "true" ||
  3422                  root.getAttribute("customize-exiting") == "true";
  3423         </getter>
  3424       </property>
  3426       <method name="_setPositionalAttributes">
  3427         <body><![CDATA[
  3428           let visibleTabs = this.tabbrowser.visibleTabs;
  3430           if (!visibleTabs.length)
  3431             return;
  3433           let selectedIndex = visibleTabs.indexOf(this.selectedItem);
  3435           let lastVisible = visibleTabs.length - 1;
  3437           if (this._afterSelectedTab)
  3438             this._afterSelectedTab.removeAttribute("afterselected-visible");
  3439           if (this.selectedItem.closing || selectedIndex == lastVisible) {
  3440             this._afterSelectedTab = null;
  3441           } else {
  3442             this._afterSelectedTab = visibleTabs[selectedIndex + 1];
  3443             this._afterSelectedTab.setAttribute("afterselected-visible",
  3444                                                 "true");
  3447           if (this._firstTab)
  3448             this._firstTab.removeAttribute("first-visible-tab");
  3449           this._firstTab = visibleTabs[0];
  3450           this._firstTab.setAttribute("first-visible-tab", "true");
  3451           if (this._lastTab)
  3452             this._lastTab.removeAttribute("last-visible-tab");
  3453           this._lastTab = visibleTabs[lastVisible];
  3454           this._lastTab.setAttribute("last-visible-tab", "true");
  3456           let hoveredTab = this._hoveredTab;
  3457           if (hoveredTab) {
  3458             hoveredTab._mouseleave();
  3459             hoveredTab._mouseenter();
  3461         ]]></body>
  3462       </method>
  3464       <field name="_blockDblClick">false</field>
  3466       <field name="_tabDropIndicator">
  3467         document.getAnonymousElementByAttribute(this, "anonid", "tab-drop-indicator");
  3468       </field>
  3470       <field name="_dragOverDelay">350</field>
  3471       <field name="_dragTime">0</field>
  3473       <field name="_container" readonly="true"><![CDATA[
  3474         this.parentNode && this.parentNode.localName == "toolbar" ? this.parentNode : this;
  3475       ]]></field>
  3477       <field name="_propagatedVisibilityOnce">false</field>
  3479       <property name="visible"
  3480                 onget="return !this._container.collapsed;">
  3481         <setter><![CDATA[
  3482           if (val == this.visible &&
  3483               this._propagatedVisibilityOnce)
  3484             return val;
  3486           this._container.collapsed = !val;
  3488           this._propagateVisibility();
  3489           this._propagatedVisibilityOnce = true;
  3491           return val;
  3492         ]]></setter>
  3493       </property>
  3495       <method name="_enterNewTab">
  3496         <body><![CDATA[
  3497           let visibleTabs = this.tabbrowser.visibleTabs;
  3498           let candidate = visibleTabs[visibleTabs.length - 1];
  3499           if (!candidate.selected) {
  3500             this._beforeHoveredTab = candidate;
  3501             candidate.setAttribute("beforehovered", "true");
  3503         ]]></body>
  3504       </method>
  3506       <method name="_leaveNewTab">
  3507         <body><![CDATA[
  3508           if (this._beforeHoveredTab) {
  3509             this._beforeHoveredTab.removeAttribute("beforehovered");
  3510             this._beforeHoveredTab = null;
  3512         ]]></body>
  3513       </method>
  3515       <method name="_propagateVisibility">
  3516         <body><![CDATA[
  3517           let visible = this.visible;
  3519           document.getElementById("menu_closeWindow").hidden = !visible;
  3520           document.getElementById("menu_close").setAttribute("label",
  3521             this.tabbrowser.mStringBundle.getString(visible ? "tabs.closeTab" : "tabs.close"));
  3523           TabsInTitlebar.allowedBy("tabs-visible", visible);
  3524         ]]></body>
  3525       </method>
  3527       <method name="updateVisibility">
  3528         <body><![CDATA[
  3529           if (this.childNodes.length - this.tabbrowser._removingTabs.length == 1)
  3530             this.visible = window.toolbar.visible;
  3531           else
  3532             this.visible = true;
  3533         ]]></body>
  3534       </method>
  3536       <method name="adjustTabstrip">
  3537         <body><![CDATA[
  3538           let numTabs = this.childNodes.length -
  3539                         this.tabbrowser._removingTabs.length;
  3540           if (numTabs > 2) {
  3541             // This is an optimization to avoid layout flushes by calling
  3542             // getBoundingClientRect() when we just opened a second tab. In
  3543             // this case it's highly unlikely that the tab width is smaller
  3544             // than mTabClipWidth and the tab close button obscures too much
  3545             // of the tab's label. In the edge case of the window being too
  3546             // narrow (or if tabClipWidth has been set to a way higher value),
  3547             // we'll correct the 'closebuttons' attribute after the tabopen
  3548             // animation has finished.
  3550             let tab = this.tabbrowser.visibleTabs[this.tabbrowser._numPinnedTabs];
  3551             if (tab && tab.getBoundingClientRect().width <= this.mTabClipWidth) {
  3552               this.setAttribute("closebuttons", "activetab");
  3553               return;
  3556           this.removeAttribute("closebuttons");
  3557         ]]></body>
  3558       </method>
  3560       <method name="_handleTabSelect">
  3561         <parameter name="aSmoothScroll"/>
  3562         <body><![CDATA[
  3563           if (this.getAttribute("overflow") == "true")
  3564             this.mTabstrip.ensureElementIsVisible(this.selectedItem, aSmoothScroll);
  3565         ]]></body>
  3566       </method>
  3568       <method name="_fillTrailingGap">
  3569         <body><![CDATA[
  3570           try {
  3571             // if we're at the right side (and not the logical end,
  3572             // which is why this works for both LTR and RTL)
  3573             // of the tabstrip, we need to ensure that we stay
  3574             // completely scrolled to the right side
  3575             var tabStrip = this.mTabstrip;
  3576             if (tabStrip.scrollPosition + tabStrip.scrollClientSize >
  3577                 tabStrip.scrollSize)
  3578               tabStrip.scrollByPixels(-1);
  3579           } catch (e) {}
  3580         ]]></body>
  3581       </method>
  3583       <field name="_closingTabsSpacer">
  3584         document.getAnonymousElementByAttribute(this, "anonid", "closing-tabs-spacer");
  3585       </field>
  3587       <field name="_tabDefaultMaxWidth">NaN</field>
  3588       <field name="_lastTabClosedByMouse">false</field>
  3589       <field name="_hasTabTempMaxWidth">false</field>
  3591       <!-- Try to keep the active tab's close button under the mouse cursor -->
  3592       <method name="_lockTabSizing">
  3593         <parameter name="aTab"/>
  3594         <body><![CDATA[
  3595           var tabs = this.tabbrowser.visibleTabs;
  3596           if (!tabs.length)
  3597             return;
  3599           var isEndTab = (aTab._tPos > tabs[tabs.length-1]._tPos);
  3600           var tabWidth = aTab.getBoundingClientRect().width;
  3602           if (!this._tabDefaultMaxWidth)
  3603             this._tabDefaultMaxWidth =
  3604               parseFloat(window.getComputedStyle(aTab).maxWidth);
  3605           this._lastTabClosedByMouse = true;
  3607           if (this.getAttribute("overflow") == "true") {
  3608             // Don't need to do anything if we're in overflow mode and aren't scrolled
  3609             // all the way to the right, or if we're closing the last tab.
  3610             if (isEndTab || !this.mTabstrip._scrollButtonDown.disabled)
  3611               return;
  3613             // If the tab has an owner that will become the active tab, the owner will
  3614             // be to the left of it, so we actually want the left tab to slide over.
  3615             // This can't be done as easily in non-overflow mode, so we don't bother.
  3616             if (aTab.owner)
  3617               return;
  3619             this._expandSpacerBy(tabWidth);
  3620           } else { // non-overflow mode
  3621             // Locking is neither in effect nor needed, so let tabs expand normally.
  3622             if (isEndTab && !this._hasTabTempMaxWidth)
  3623               return;
  3625             let numPinned = this.tabbrowser._numPinnedTabs;
  3626             // Force tabs to stay the same width, unless we're closing the last tab,
  3627             // which case we need to let them expand just enough so that the overall
  3628             // tabbar width is the same.
  3629             if (isEndTab) {
  3630               let numNormalTabs = tabs.length - numPinned;
  3631               tabWidth = tabWidth * (numNormalTabs + 1) / numNormalTabs;
  3632               if (tabWidth > this._tabDefaultMaxWidth)
  3633                 tabWidth = this._tabDefaultMaxWidth;
  3635             tabWidth += "px";
  3636             for (let i = numPinned; i < tabs.length; i++) {
  3637               let tab = tabs[i];
  3638               tab.style.setProperty("max-width", tabWidth, "important");
  3639               if (!isEndTab) { // keep tabs the same width
  3640                 tab.style.transition = "none";
  3641                 tab.clientTop; // flush styles to skip animation; see bug 649247
  3642                 tab.style.transition = "";
  3645             this._hasTabTempMaxWidth = true;
  3646             this.tabbrowser.addEventListener("mousemove", this, false);
  3647             window.addEventListener("mouseout", this, false);
  3649         ]]></body>
  3650       </method>
  3652       <method name="_expandSpacerBy">
  3653         <parameter name="pixels"/>
  3654         <body><![CDATA[
  3655           let spacer = this._closingTabsSpacer;
  3656           spacer.style.width = parseFloat(spacer.style.width) + pixels + "px";
  3657           this.setAttribute("using-closing-tabs-spacer", "true");
  3658           this.tabbrowser.addEventListener("mousemove", this, false);
  3659           window.addEventListener("mouseout", this, false);
  3660         ]]></body>
  3661       </method>
  3663       <method name="_unlockTabSizing">
  3664         <body><![CDATA[
  3665           this.tabbrowser.removeEventListener("mousemove", this, false);
  3666           window.removeEventListener("mouseout", this, false);
  3668           if (this._hasTabTempMaxWidth) {
  3669             this._hasTabTempMaxWidth = false;
  3670             let tabs = this.tabbrowser.visibleTabs;
  3671             for (let i = 0; i < tabs.length; i++)
  3672               tabs[i].style.maxWidth = "";
  3675           if (this.hasAttribute("using-closing-tabs-spacer")) {
  3676             this.removeAttribute("using-closing-tabs-spacer");
  3677             this._closingTabsSpacer.style.width = 0;
  3679         ]]></body>
  3680       </method>
  3682       <field name="_lastNumPinned">0</field>
  3683       <method name="_positionPinnedTabs">
  3684         <body><![CDATA[
  3685           var numPinned = this.tabbrowser._numPinnedTabs;
  3686           var doPosition = this.getAttribute("overflow") == "true" &&
  3687                            numPinned > 0;
  3689           if (doPosition) {
  3690             this.setAttribute("positionpinnedtabs", "true");
  3692             let scrollButtonWidth = this.mTabstrip._scrollButtonDown.getBoundingClientRect().width;
  3693             let paddingStart = this.mTabstrip.scrollboxPaddingStart;
  3694             let width = 0;
  3696             for (let i = numPinned - 1; i >= 0; i--) {
  3697               let tab = this.childNodes[i];
  3698               width += tab.getBoundingClientRect().width;
  3699               tab.style.MozMarginStart = - (width + scrollButtonWidth + paddingStart) + "px";
  3702             this.style.MozPaddingStart = width + paddingStart + "px";
  3704           } else {
  3705             this.removeAttribute("positionpinnedtabs");
  3707             for (let i = 0; i < numPinned; i++) {
  3708               let tab = this.childNodes[i];
  3709               tab.style.MozMarginStart = "";
  3712             this.style.MozPaddingStart = "";
  3715           if (this._lastNumPinned != numPinned) {
  3716             this._lastNumPinned = numPinned;
  3717             this._handleTabSelect(false);
  3719         ]]></body>
  3720       </method>
  3722       <method name="_animateTabMove">
  3723         <parameter name="event"/>
  3724         <body><![CDATA[
  3725           let draggedTab = event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0);
  3727           if (this.getAttribute("movingtab") != "true") {
  3728             this.setAttribute("movingtab", "true");
  3729             this.selectedItem = draggedTab;
  3732           if (!("animLastScreenX" in draggedTab._dragData))
  3733             draggedTab._dragData.animLastScreenX = draggedTab._dragData.screenX;
  3735           let screenX = event.screenX;
  3736           if (screenX == draggedTab._dragData.animLastScreenX)
  3737             return;
  3739           let draggingRight = screenX > draggedTab._dragData.animLastScreenX;
  3740           draggedTab._dragData.animLastScreenX = screenX;
  3742           let rtl = (window.getComputedStyle(this).direction == "rtl");
  3743           let pinned = draggedTab.pinned;
  3744           let numPinned = this.tabbrowser._numPinnedTabs;
  3745           let tabs = this.tabbrowser.visibleTabs
  3746                                     .slice(pinned ? 0 : numPinned,
  3747                                            pinned ? numPinned : undefined);
  3748           if (rtl)
  3749             tabs.reverse();
  3750           let tabWidth = draggedTab.getBoundingClientRect().width;
  3752           // Move the dragged tab based on the mouse position.
  3754           let leftTab = tabs[0];
  3755           let rightTab = tabs[tabs.length - 1];
  3756           let tabScreenX = draggedTab.boxObject.screenX;
  3757           let translateX = screenX - draggedTab._dragData.screenX;
  3758           if (!pinned)
  3759             translateX += this.mTabstrip.scrollPosition - draggedTab._dragData.scrollX;
  3760           let leftBound = leftTab.boxObject.screenX - tabScreenX;
  3761           let rightBound = (rightTab.boxObject.screenX + rightTab.boxObject.width) -
  3762                            (tabScreenX + tabWidth);
  3763           translateX = Math.max(translateX, leftBound);
  3764           translateX = Math.min(translateX, rightBound);
  3765           draggedTab.style.transform = "translateX(" + translateX + "px)";
  3767           // Determine what tab we're dragging over.
  3768           // * Point of reference is the center of the dragged tab. If that
  3769           //   point touches a background tab, the dragged tab would take that
  3770           //   tab's position when dropped.
  3771           // * We're doing a binary search in order to reduce the amount of
  3772           //   tabs we need to check.
  3774           let tabCenter = tabScreenX + translateX + tabWidth / 2;
  3775           let newIndex = -1;
  3776           let oldIndex = "animDropIndex" in draggedTab._dragData ?
  3777                          draggedTab._dragData.animDropIndex : draggedTab._tPos;
  3778           let low = 0;
  3779           let high = tabs.length - 1;
  3780           while (low <= high) {
  3781             let mid = Math.floor((low + high) / 2);
  3782             if (tabs[mid] == draggedTab &&
  3783                 ++mid > high)
  3784               break;
  3785             let boxObject = tabs[mid].boxObject;
  3786             let screenX = boxObject.screenX + getTabShift(tabs[mid], oldIndex);
  3787             if (screenX > tabCenter) {
  3788               high = mid - 1;
  3789             } else if (screenX + boxObject.width < tabCenter) {
  3790               low = mid + 1;
  3791             } else {
  3792               newIndex = tabs[mid]._tPos;
  3793               break;
  3796           if (newIndex >= oldIndex)
  3797             newIndex++;
  3798           if (newIndex < 0 || newIndex == oldIndex)
  3799             return;
  3800           draggedTab._dragData.animDropIndex = newIndex;
  3802           // Shift background tabs to leave a gap where the dragged tab
  3803           // would currently be dropped.
  3805           for (let tab of tabs) {
  3806             if (tab != draggedTab) {
  3807               let shift = getTabShift(tab, newIndex);
  3808               tab.style.transform = shift ? "translateX(" + shift + "px)" : "";
  3812           function getTabShift(tab, dropIndex) {
  3813             if (tab._tPos < draggedTab._tPos && tab._tPos >= dropIndex)
  3814               return rtl ? -tabWidth : tabWidth;
  3815             if (tab._tPos > draggedTab._tPos && tab._tPos < dropIndex)
  3816               return rtl ? tabWidth : -tabWidth;
  3817             return 0;
  3819         ]]></body>
  3820       </method>
  3822       <method name="_finishAnimateTabMove">
  3823         <body><![CDATA[
  3824           if (this.getAttribute("movingtab") != "true")
  3825             return;
  3827           for (let tab of this.tabbrowser.visibleTabs)
  3828             tab.style.transform = "";
  3830           this.removeAttribute("movingtab");
  3832           this._handleTabSelect();
  3833         ]]></body>
  3834       </method>
  3836       <method name="handleEvent">
  3837         <parameter name="aEvent"/>
  3838         <body><![CDATA[
  3839           switch (aEvent.type) {
  3840             case "load":
  3841               this.updateVisibility();
  3842               break;
  3843             case "resize":
  3844               if (aEvent.target != window)
  3845                 break;
  3847               TabsInTitlebar.updateAppearance();
  3849               if (this.tabbrowser.visibleTabs.length > 1) {
  3850                 var width = this.mTabstrip.boxObject.width;
  3851                 if (width != this.mTabstripWidth) {
  3852                   this.adjustTabstrip();
  3853                   this._fillTrailingGap();
  3854                   this._handleTabSelect();
  3855                   this.mTabstripWidth = width;
  3859               this.tabbrowser.updateWindowResizers();
  3860               break;
  3861             case "mouseout":
  3862               // If the "related target" (the node to which the pointer went) is not
  3863               // a child of the current document, the mouse just left the window.
  3864               let relatedTarget = aEvent.relatedTarget;
  3865               if (relatedTarget && relatedTarget.ownerDocument == document)
  3866                 break;
  3867             case "mousemove":
  3868               if (document.getElementById("tabContextMenu").state != "open")
  3869                 this._unlockTabSizing();
  3870               break;
  3872         ]]></body>
  3873       </method>
  3875       <field name="_animateElement">
  3876         this.mTabstrip._scrollButtonDown;
  3877       </field>
  3879       <method name="_notifyBackgroundTab">
  3880         <parameter name="aTab"/>
  3881         <body><![CDATA[
  3882           if (aTab.pinned)
  3883             return;
  3885           var scrollRect = this.mTabstrip.scrollClientRect;
  3886           var tab = aTab.getBoundingClientRect();
  3887           this.mTabstrip._calcTabMargins(aTab);
  3889           // DOMRect left/right properties are immutable.
  3890           tab = {left: tab.left, right: tab.right};
  3892           // Is the new tab already completely visible?
  3893           if (scrollRect.left <= tab.left && tab.right <= scrollRect.right)
  3894             return;
  3896           if (this.mTabstrip.smoothScroll) {
  3897             let selected = !this.selectedItem.pinned &&
  3898                            this.selectedItem.getBoundingClientRect();
  3899             if (selected) {
  3900               selected = {left: selected.left, right: selected.right};
  3901               // Need to take in to account the width of the left/right margins on tabs.
  3902               selected.left = selected.left + this.mTabstrip._tabMarginLeft;
  3903               selected.right = selected.right - this.mTabstrip._tabMarginRight;
  3906             tab.left += this.mTabstrip._tabMarginLeft;
  3907             tab.right -= this.mTabstrip._tabMarginRight;
  3909             // Can we make both the new tab and the selected tab completely visible?
  3910             if (!selected ||
  3911                 Math.max(tab.right - selected.left, selected.right - tab.left) <=
  3912                   scrollRect.width) {
  3913               this.mTabstrip.ensureElementIsVisible(aTab);
  3914               return;
  3917             this.mTabstrip._smoothScrollByPixels(this.mTabstrip._isRTLScrollbox ?
  3918                                                  selected.right - scrollRect.right :
  3919                                                  selected.left - scrollRect.left);
  3922           if (!this._animateElement.hasAttribute("notifybgtab")) {
  3923             this._animateElement.setAttribute("notifybgtab", "true");
  3924             setTimeout(function (ele) {
  3925               ele.removeAttribute("notifybgtab");
  3926             }, 150, this._animateElement);
  3928         ]]></body>
  3929       </method>
  3931       <method name="_getDragTargetTab">
  3932         <parameter name="event"/>
  3933         <body><![CDATA[
  3934           let tab = event.target.localName == "tab" ? event.target : null;
  3935           if (tab &&
  3936               (event.type == "drop" || event.type == "dragover") &&
  3937               event.dataTransfer.dropEffect == "link") {
  3938             let boxObject = tab.boxObject;
  3939             if (event.screenX < boxObject.screenX + boxObject.width * .25 ||
  3940                 event.screenX > boxObject.screenX + boxObject.width * .75)
  3941               return null;
  3943           return tab;
  3944         ]]></body>
  3945       </method>
  3947       <method name="_getDropIndex">
  3948         <parameter name="event"/>
  3949         <body><![CDATA[
  3950           var tabs = this.childNodes;
  3951           var tab = this._getDragTargetTab(event);
  3952           if (window.getComputedStyle(this, null).direction == "ltr") {
  3953             for (let i = tab ? tab._tPos : 0; i < tabs.length; i++)
  3954               if (event.screenX < tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2)
  3955                 return i;
  3956           } else {
  3957             for (let i = tab ? tab._tPos : 0; i < tabs.length; i++)
  3958               if (event.screenX > tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2)
  3959                 return i;
  3961           return tabs.length;
  3962         ]]></body>
  3963       </method>
  3965       <method name="_setEffectAllowedForDataTransfer">
  3966         <parameter name="event"/>
  3967         <body><![CDATA[
  3968           var dt = event.dataTransfer;
  3969           // Disallow dropping multiple items
  3970           if (dt.mozItemCount > 1)
  3971             return dt.effectAllowed = "none";
  3973           var types = dt.mozTypesAt(0);
  3974           var sourceNode = null;
  3975           // tabs are always added as the first type
  3976           if (types[0] == TAB_DROP_TYPE) {
  3977             var sourceNode = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
  3978             if (sourceNode instanceof XULElement &&
  3979                 sourceNode.localName == "tab" &&
  3980                 sourceNode.ownerDocument.defaultView instanceof ChromeWindow &&
  3981                 sourceNode.ownerDocument.documentElement.getAttribute("windowtype") == "navigator:browser" &&
  3982                 sourceNode.ownerDocument.defaultView.gBrowser.tabContainer == sourceNode.parentNode) {
  3983               // Do not allow transfering a private tab to a non-private window
  3984               // and vice versa.
  3985               if (PrivateBrowsingUtils.isWindowPrivate(window) !=
  3986                   PrivateBrowsingUtils.isWindowPrivate(sourceNode.ownerDocument.defaultView))
  3987                 return dt.effectAllowed = "none";
  3989               if (window.gMultiProcessBrowser !=
  3990                   sourceNode.ownerDocument.defaultView.gMultiProcessBrowser)
  3991                 return dt.effectAllowed = "none";
  3993 #ifdef XP_MACOSX
  3994               return dt.effectAllowed = event.altKey ? "copy" : "move";
  3995 #else
  3996               return dt.effectAllowed = event.ctrlKey ? "copy" : "move";
  3997 #endif
  4001           if (browserDragAndDrop.canDropLink(event)) {
  4002             // Here we need to do this manually
  4003             return dt.effectAllowed = dt.dropEffect = "link";
  4005           return dt.effectAllowed = "none";
  4006         ]]></body>
  4007       </method>
  4009       <method name="_handleNewTab">
  4010         <parameter name="tab"/>
  4011         <body><![CDATA[
  4012           if (tab.parentNode != this)
  4013             return;
  4014           tab._fullyOpen = true;
  4016           this.adjustTabstrip();
  4018           if (tab.getAttribute("selected") == "true") {
  4019             this._fillTrailingGap();
  4020             this._handleTabSelect();
  4021           } else {
  4022             this._notifyBackgroundTab(tab);
  4025           // XXXmano: this is a temporary workaround for bug 345399
  4026           // We need to manually update the scroll buttons disabled state
  4027           // if a tab was inserted to the overflow area or removed from it
  4028           // without any scrolling and when the tabbar has already
  4029           // overflowed.
  4030           this.mTabstrip._updateScrollButtonsDisabledState();
  4031         ]]></body>
  4032       </method>
  4034       <method name="_canAdvanceToTab">
  4035         <parameter name="aTab"/>
  4036         <body>
  4037         <![CDATA[
  4038           return !aTab.closing;
  4039         ]]>
  4040         </body>
  4041       </method>
  4043       <method name="_handleTabTelemetryStart">
  4044         <parameter name="aTab"/>
  4045         <parameter name="aURI"/>
  4046         <body>
  4047         <![CDATA[
  4048           // Animation-smoothness telemetry/logging
  4049           if (Services.telemetry.canRecord || this._tabAnimationLoggingEnabled) {
  4050             if (aURI == "about:newtab" && (aTab._tPos == 1 || aTab._tPos == 2)) {
  4051               // Indicate newtab page animation where other tabs are unaffected
  4052               // (for which case, the 2nd or 3rd tabs are good representatives, even if not absolute)
  4053               aTab._recordingTabOpenPlain = true;
  4055             aTab._recordingHandle = window.QueryInterface(Ci.nsIInterfaceRequestor)
  4056                                           .getInterface(Ci.nsIDOMWindowUtils)
  4057                                           .startFrameTimeRecording();
  4060           // Overall animation duration
  4061           aTab._animStartTime = Date.now();
  4062         ]]>
  4063         </body>
  4064       </method>
  4066       <method name="_handleTabTelemetryEnd">
  4067         <parameter name="aTab"/>
  4068         <body>
  4069         <![CDATA[
  4070           if (!aTab._animStartTime) {
  4071             return;
  4074           Services.telemetry.getHistogramById(aTab.closing ?
  4075                                               "FX_TAB_ANIM_CLOSE_MS" :
  4076                                               "FX_TAB_ANIM_OPEN_MS")
  4077                             .add(Date.now() - aTab._animStartTime);
  4078           aTab._animStartTime = 0;
  4080           // Handle tab animation smoothness telemetry/logging of frame intervals and paint times
  4081           if (!("_recordingHandle" in aTab)) {
  4082             return;
  4085           let intervals = window.QueryInterface(Ci.nsIInterfaceRequestor)
  4086                                 .getInterface(Ci.nsIDOMWindowUtils)
  4087                                 .stopFrameTimeRecording(aTab._recordingHandle);
  4088           delete aTab._recordingHandle;
  4089           let frameCount = intervals.length;
  4091           if (this._tabAnimationLoggingEnabled) {
  4092             let msg = "Tab " + (aTab.closing ? "close" : "open") + " (Frame-interval):\n";
  4093             for (let i = 0; i < frameCount; i++) {
  4094               msg += Math.round(intervals[i]) + "\n";
  4096             Services.console.logStringMessage(msg);
  4099           // For telemetry, the first frame interval is not useful since it may represent an interval
  4100           // to a relatively old frame (prior to recording start). So we'll ignore it for the average.
  4101           if (frameCount > 1) {
  4102             let averageInterval = 0;
  4103             for (let i = 1; i < frameCount; i++) {
  4104               averageInterval += intervals[i];
  4105             };
  4106             averageInterval = averageInterval / (frameCount - 1);
  4108             Services.telemetry.getHistogramById("FX_TAB_ANIM_ANY_FRAME_INTERVAL_MS").add(averageInterval);
  4110             if (aTab._recordingTabOpenPlain) {
  4111               delete aTab._recordingTabOpenPlain;
  4112               // While we do have a telemetry probe NEWTAB_PAGE_ENABLED to monitor newtab preview, it'll be
  4113               // easier to overview the data without slicing by it. Hence the additional histograms with _PREVIEW.
  4114               let preview = this._browserNewtabpageEnabled ? "_PREVIEW" : "";
  4115               Services.telemetry.getHistogramById("FX_TAB_ANIM_OPEN" + preview + "_FRAME_INTERVAL_MS").add(averageInterval);
  4118         ]]>
  4119         </body>
  4120       </method>
  4122       <!-- Deprecated stuff, implemented for backwards compatibility. -->
  4123       <property name="mTabstripClosebutton" readonly="true"
  4124                 onget="return document.getElementById('tabs-closebutton');"/>
  4125       <property name="mAllTabsPopup" readonly="true"
  4126                 onget="return document.getElementById('alltabs-popup');"/>
  4127     </implementation>
  4129     <handlers>
  4130       <handler event="TabSelect" action="this._handleTabSelect();"/>
  4132       <handler event="transitionend"><![CDATA[
  4133         if (event.propertyName != "max-width")
  4134           return;
  4136         var tab = event.target;
  4138         this._handleTabTelemetryEnd(tab);
  4140         if (tab.getAttribute("fadein") == "true") {
  4141           if (tab._fullyOpen)
  4142             this.adjustTabstrip();
  4143           else
  4144             this._handleNewTab(tab);
  4145         } else if (tab.closing) {
  4146           this.tabbrowser._endRemoveTab(tab);
  4148       ]]></handler>
  4150       <handler event="dblclick"><![CDATA[
  4151 #ifndef XP_MACOSX
  4152         // When the tabbar has an unified appearance with the titlebar
  4153         // and menubar, a double-click in it should have the same behavior
  4154         // as double-clicking the titlebar
  4155         if (TabsInTitlebar.enabled || this.parentNode._dragBindingAlive)
  4156           return;
  4157 #endif
  4159         if (event.button != 0 ||
  4160             event.originalTarget.localName != "box")
  4161           return;
  4163         // See hack note in the tabbrowser-close-tab-button binding
  4164         if (!this._blockDblClick)
  4165           BrowserOpenTab();
  4167         event.preventDefault();
  4168       ]]></handler>
  4170       <handler event="click" button="0" phase="capturing"><![CDATA[
  4171         /* Catches extra clicks meant for the in-tab close button.
  4172          * Placed here to avoid leaking (a temporary handler added from the
  4173          * in-tab close button binding would close over the tab and leak it
  4174          * until the handler itself was removed). (bug 897751)
  4176          * The only sequence in which a second click event (i.e. dblclik)
  4177          * can be dispatched on an in-tab close button is when it is shown
  4178          * after the first click (i.e. the first click event was dispatched
  4179          * on the tab). This happens when we show the close button only on
  4180          * the active tab. (bug 352021)
  4181          * The only sequence in which a third click event can be dispatched
  4182          * on an in-tab close button is when the tab was opened with a
  4183          * double click on the tabbar. (bug 378344)
  4184          * In both cases, it is most likely that the close button area has
  4185          * been accidentally clicked, therefore we do not close the tab.
  4187          * We don't want to ignore processing of more than one click event,
  4188          * though, since the user might actually be repeatedly clicking to
  4189          * close many tabs at once.
  4190          */
  4191         let target = event.originalTarget;
  4192         if (target.classList.contains('tab-close-button')) {
  4193           // We preemptively set this to allow the closing-multiple-tabs-
  4194           // in-a-row case.
  4195           if (this._blockDblClick) {
  4196             target._ignoredCloseButtonClicks = true;
  4197           } else if (event.detail > 1 && !target._ignoredCloseButtonClicks) {
  4198             target._ignoredCloseButtonClicks = true;
  4199             event.stopPropagation();
  4200             return;
  4201           } else {
  4202             // Reset the "ignored click" flag
  4203             target._ignoredCloseButtonClicks = false;
  4207         /* Protects from close-tab-button errant doubleclick:
  4208          * Since we're removing the event target, if the user
  4209          * double-clicks the button, the dblclick event will be dispatched
  4210          * with the tabbar as its event target (and explicit/originalTarget),
  4211          * which treats that as a mouse gesture for opening a new tab.
  4212          * In this context, we're manually blocking the dblclick event
  4213          * (see tabbrowser-close-tab-button dblclick handler).
  4214          */
  4215         if (this._blockDblClick) {
  4216           if (!("_clickedTabBarOnce" in this)) {
  4217             this._clickedTabBarOnce = true;
  4218             return;
  4220           delete this._clickedTabBarOnce;
  4221           this._blockDblClick = false;
  4223       ]]></handler>
  4225       <handler event="click"><![CDATA[
  4226         if (event.button != 1)
  4227           return;
  4229         if (event.target.localName == "tab") {
  4230           this.tabbrowser.removeTab(event.target, {animate: true, byMouse: true});
  4231         } else if (event.originalTarget.localName == "box") {
  4232           BrowserOpenTab();
  4233         } else {
  4234           return;
  4237         event.stopPropagation();
  4238       ]]></handler>
  4240       <handler event="keypress"><![CDATA[
  4241         if (event.altKey || event.shiftKey ||
  4242 #ifdef XP_MACOSX
  4243             !event.metaKey)
  4244 #else
  4245             !event.ctrlKey || event.metaKey)
  4246 #endif
  4247           return;
  4249         switch (event.keyCode) {
  4250           case KeyEvent.DOM_VK_UP:
  4251             this.tabbrowser.moveTabBackward();
  4252             break;
  4253           case KeyEvent.DOM_VK_DOWN:
  4254             this.tabbrowser.moveTabForward();
  4255             break;
  4256           case KeyEvent.DOM_VK_RIGHT:
  4257           case KeyEvent.DOM_VK_LEFT:
  4258             this.tabbrowser.moveTabOver(event);
  4259             break;
  4260           case KeyEvent.DOM_VK_HOME:
  4261             this.tabbrowser.moveTabToStart();
  4262             break;
  4263           case KeyEvent.DOM_VK_END:
  4264             this.tabbrowser.moveTabToEnd();
  4265             break;
  4266           default:
  4267             // Stop the keypress event for the above keyboard
  4268             // shortcuts only.
  4269             return;
  4271         event.stopPropagation();
  4272         event.preventDefault();
  4273       ]]></handler>
  4275       <handler event="dragstart"><![CDATA[
  4276         var tab = this._getDragTargetTab(event);
  4277         if (!tab || this._isCustomizing)
  4278           return;
  4280         let dt = event.dataTransfer;
  4281         dt.mozSetDataAt(TAB_DROP_TYPE, tab, 0);
  4282         let browser = tab.linkedBrowser;
  4284         // We must not set text/x-moz-url or text/plain data here,
  4285         // otherwise trying to deatch the tab by dropping it on the desktop
  4286         // may result in an "internet shortcut"
  4287         dt.mozSetDataAt("text/x-moz-text-internal", browser.currentURI.spec, 0);
  4289         // Set the cursor to an arrow during tab drags.
  4290         dt.mozCursor = "default";
  4292         // Create a canvas to which we capture the current tab.
  4293         // Until canvas is HiDPI-aware (bug 780362), we need to scale the desired
  4294         // canvas size (in CSS pixels) to the window's backing resolution in order
  4295         // to get a full-resolution drag image for use on HiDPI displays.
  4296         let windowUtils = window.getInterface(Ci.nsIDOMWindowUtils);
  4297         let scale = windowUtils.screenPixelsPerCSSPixel / windowUtils.fullZoom;
  4298         let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
  4299         canvas.mozOpaque = true;
  4300         canvas.width = 160 * scale;
  4301         canvas.height = 90 * scale;
  4302         PageThumbs.captureToCanvas(browser.contentWindow, canvas);
  4303         dt.setDragImage(canvas, -16 * scale, -16 * scale);
  4305         // _dragData.offsetX/Y give the coordinates that the mouse should be
  4306         // positioned relative to the corner of the new window created upon
  4307         // dragend such that the mouse appears to have the same position
  4308         // relative to the corner of the dragged tab.
  4309         function clientX(ele) ele.getBoundingClientRect().left;
  4310         let tabOffsetX = clientX(tab) - clientX(this);
  4311         tab._dragData = {
  4312           offsetX: event.screenX - window.screenX - tabOffsetX,
  4313           offsetY: event.screenY - window.screenY,
  4314           scrollX: this.mTabstrip.scrollPosition,
  4315           screenX: event.screenX
  4316         };
  4318         event.stopPropagation();
  4319       ]]></handler>
  4321       <handler event="dragover"><![CDATA[
  4322         var effects = this._setEffectAllowedForDataTransfer(event);
  4324         var ind = this._tabDropIndicator;
  4325         if (effects == "" || effects == "none") {
  4326           ind.collapsed = true;
  4327           return;
  4329         event.preventDefault();
  4330         event.stopPropagation();
  4332         var tabStrip = this.mTabstrip;
  4333         var ltr = (window.getComputedStyle(this, null).direction == "ltr");
  4335         // autoscroll the tab strip if we drag over the scroll
  4336         // buttons, even if we aren't dragging a tab, but then
  4337         // return to avoid drawing the drop indicator
  4338         var pixelsToScroll = 0;
  4339         if (this.getAttribute("overflow") == "true") {
  4340           var targetAnonid = event.originalTarget.getAttribute("anonid");
  4341           switch (targetAnonid) {
  4342             case "scrollbutton-up":
  4343               pixelsToScroll = tabStrip.scrollIncrement * -1;
  4344               break;
  4345             case "scrollbutton-down":
  4346               pixelsToScroll = tabStrip.scrollIncrement;
  4347               break;
  4349           if (pixelsToScroll)
  4350             tabStrip.scrollByPixels((ltr ? 1 : -1) * pixelsToScroll);
  4353         if (effects == "move" &&
  4354             this == event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0).parentNode) {
  4355           ind.collapsed = true;
  4356           this._animateTabMove(event);
  4357           return;
  4360         this._finishAnimateTabMove();
  4362         if (effects == "link") {
  4363           let tab = this._getDragTargetTab(event);
  4364           if (tab) {
  4365             if (!this._dragTime)
  4366               this._dragTime = Date.now();
  4367             if (Date.now() >= this._dragTime + this._dragOverDelay)
  4368               this.selectedItem = tab;
  4369             ind.collapsed = true;
  4370             return;
  4374         var rect = tabStrip.getBoundingClientRect();
  4375         var newMargin;
  4376         if (pixelsToScroll) {
  4377           // if we are scrolling, put the drop indicator at the edge
  4378           // so that it doesn't jump while scrolling
  4379           let scrollRect = tabStrip.scrollClientRect;
  4380           let minMargin = scrollRect.left - rect.left;
  4381           let maxMargin = Math.min(minMargin + scrollRect.width,
  4382                                    scrollRect.right);
  4383           if (!ltr)
  4384             [minMargin, maxMargin] = [this.clientWidth - maxMargin,
  4385                                       this.clientWidth - minMargin];
  4386           newMargin = (pixelsToScroll > 0) ? maxMargin : minMargin;
  4388         else {
  4389           let newIndex = this._getDropIndex(event);
  4390           if (newIndex == this.childNodes.length) {
  4391             let tabRect = this.childNodes[newIndex-1].getBoundingClientRect();
  4392             if (ltr)
  4393               newMargin = tabRect.right - rect.left;
  4394             else
  4395               newMargin = rect.right - tabRect.left;
  4397           else {
  4398             let tabRect = this.childNodes[newIndex].getBoundingClientRect();
  4399             if (ltr)
  4400               newMargin = tabRect.left - rect.left;
  4401             else
  4402               newMargin = rect.right - tabRect.right;
  4406         ind.collapsed = false;
  4408         newMargin += ind.clientWidth / 2;
  4409         if (!ltr)
  4410           newMargin *= -1;
  4412         ind.style.transform = "translate(" + Math.round(newMargin) + "px)";
  4413         ind.style.MozMarginStart = (-ind.clientWidth) + "px";
  4414       ]]></handler>
  4416       <handler event="drop"><![CDATA[
  4417         var dt = event.dataTransfer;
  4418         var dropEffect = dt.dropEffect;
  4419         var draggedTab;
  4420         if (dropEffect != "link") { // copy or move
  4421           draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
  4422           // not our drop then
  4423           if (!draggedTab)
  4424             return;
  4427         this._tabDropIndicator.collapsed = true;
  4428         event.stopPropagation();
  4429         if (draggedTab && dropEffect == "copy") {
  4430           // copy the dropped tab (wherever it's from)
  4431           let newIndex = this._getDropIndex(event);
  4432           let newTab = this.tabbrowser.duplicateTab(draggedTab);
  4433           this.tabbrowser.moveTabTo(newTab, newIndex);
  4434           if (draggedTab.parentNode != this || event.shiftKey)
  4435             this.selectedItem = newTab;
  4436         } else if (draggedTab && draggedTab.parentNode == this) {
  4437           this._finishAnimateTabMove();
  4439           // actually move the dragged tab
  4440           if ("animDropIndex" in draggedTab._dragData) {
  4441             let newIndex = draggedTab._dragData.animDropIndex;
  4442             if (newIndex > draggedTab._tPos)
  4443               newIndex--;
  4444             this.tabbrowser.moveTabTo(draggedTab, newIndex);
  4446         } else if (draggedTab) {
  4447           // swap the dropped tab with a new one we create and then close
  4448           // it in the other window (making it seem to have moved between
  4449           // windows)
  4450           let newIndex = this._getDropIndex(event);
  4451           let newTab = this.tabbrowser.addTab("about:blank");
  4452           let newBrowser = this.tabbrowser.getBrowserForTab(newTab);
  4453           // Stop the about:blank load
  4454           newBrowser.stop();
  4455           // make sure it has a docshell
  4456           newBrowser.docShell;
  4458           let numPinned = this.tabbrowser._numPinnedTabs;
  4459           if (newIndex < numPinned || draggedTab.pinned && newIndex == numPinned)
  4460             this.tabbrowser.pinTab(newTab);
  4461           this.tabbrowser.moveTabTo(newTab, newIndex);
  4463           // We need to select the tab before calling swapBrowsersAndCloseOther
  4464           // so that window.content in chrome windows points to the right tab
  4465           // when pagehide/show events are fired.
  4466           this.tabbrowser.selectedTab = newTab;
  4468           draggedTab.parentNode._finishAnimateTabMove();
  4469           this.tabbrowser.swapBrowsersAndCloseOther(newTab, draggedTab);
  4471           // Call updateCurrentBrowser to make sure the URL bar is up to date
  4472           // for our new tab after we've done swapBrowsersAndCloseOther.
  4473           this.tabbrowser.updateCurrentBrowser(true);
  4474         } else {
  4475           // Pass true to disallow dropping javascript: or data: urls
  4476           let url;
  4477           try {
  4478             url = browserDragAndDrop.drop(event, { }, true);
  4479           } catch (ex) {}
  4481           if (!url)
  4482             return;
  4484           let bgLoad = Services.prefs.getBoolPref("browser.tabs.loadInBackground");
  4486           if (event.shiftKey)
  4487             bgLoad = !bgLoad;
  4489           let tab = this._getDragTargetTab(event);
  4490           if (!tab || dropEffect == "copy") {
  4491             // We're adding a new tab.
  4492             let newIndex = this._getDropIndex(event);
  4493             let newTab = this.tabbrowser.loadOneTab(url, {inBackground: bgLoad, allowThirdPartyFixup: true});
  4494             this.tabbrowser.moveTabTo(newTab, newIndex);
  4495           } else {
  4496             // Load in an existing tab.
  4497             try {
  4498               let webNav = Ci.nsIWebNavigation;
  4499               let flags = webNav.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP |
  4500                           webNav.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
  4501               this.tabbrowser.getBrowserForTab(tab).loadURIWithFlags(url, flags);
  4502               if (!bgLoad)
  4503                 this.selectedItem = tab;
  4504             } catch(ex) {
  4505               // Just ignore invalid urls
  4510         if (draggedTab) {
  4511           delete draggedTab._dragData;
  4513       ]]></handler>
  4515       <handler event="dragend"><![CDATA[
  4516         // Note: while this case is correctly handled here, this event
  4517         // isn't dispatched when the tab is moved within the tabstrip,
  4518         // see bug 460801.
  4520         this._finishAnimateTabMove();
  4522         var dt = event.dataTransfer;
  4523         var draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
  4524         if (dt.mozUserCancelled || dt.dropEffect != "none" || this._isCustomizing) {
  4525           delete draggedTab._dragData;
  4526           return;
  4529         // Disable detach within the browser toolbox
  4530         var eX = event.screenX;
  4531         var eY = event.screenY;
  4532         var wX = window.screenX;
  4533         // check if the drop point is horizontally within the window
  4534         if (eX > wX && eX < (wX + window.outerWidth)) {
  4535           let bo = this.mTabstrip.boxObject;
  4536           // also avoid detaching if the the tab was dropped too close to
  4537           // the tabbar (half a tab)
  4538           let endScreenY = bo.screenY + 1.5 * bo.height;
  4539           if (eY < endScreenY && eY > window.screenY)
  4540             return;
  4543         // screen.availLeft et. al. only check the screen that this window is on,
  4544         // but we want to look at the screen the tab is being dropped onto.
  4545         var sX = {}, sY = {}, sWidth = {}, sHeight = {};
  4546         Cc["@mozilla.org/gfx/screenmanager;1"]
  4547           .getService(Ci.nsIScreenManager)
  4548           .screenForRect(eX, eY, 1, 1)
  4549           .GetAvailRect(sX, sY, sWidth, sHeight);
  4550         // ensure new window entirely within screen
  4551         var winWidth = Math.min(window.outerWidth, sWidth.value);
  4552         var winHeight = Math.min(window.outerHeight, sHeight.value);
  4553         var left = Math.min(Math.max(eX - draggedTab._dragData.offsetX, sX.value),
  4554                             sX.value + sWidth.value - winWidth);
  4555         var top = Math.min(Math.max(eY - draggedTab._dragData.offsetY, sY.value),
  4556                            sY.value + sHeight.value - winHeight);
  4558         delete draggedTab._dragData;
  4560         if (this.tabbrowser.tabs.length == 1) {
  4561           // resize _before_ move to ensure the window fits the new screen.  if
  4562           // the window is too large for its screen, the window manager may do
  4563           // automatic repositioning.
  4564           window.resizeTo(winWidth, winHeight);
  4565           window.moveTo(left, top);
  4566           window.focus();
  4567         } else {
  4568           this.tabbrowser.replaceTabWithWindow(draggedTab, { screenX: left,
  4569                                                              screenY: top,
  4570 #ifndef XP_WIN
  4571                                                              outerWidth: winWidth,
  4572                                                              outerHeight: winHeight
  4573 #endif
  4574                                                              });
  4576         event.stopPropagation();
  4577       ]]></handler>
  4579       <handler event="dragexit"><![CDATA[
  4580         this._dragTime = 0;
  4582         // This does not work at all (see bug 458613)
  4583         var target = event.relatedTarget;
  4584         while (target && target != this)
  4585           target = target.parentNode;
  4586         if (target)
  4587           return;
  4589         this._tabDropIndicator.collapsed = true;
  4590         event.stopPropagation();
  4591       ]]></handler>
  4592     </handlers>
  4593   </binding>
  4595   <!-- close-tab-button binding
  4596        This binding relies on the structure of the tabbrowser binding.
  4597        Therefore it should only be used as a child of the tab or the tabs
  4598        element (in both cases, when they are anonymous nodes of <tabbrowser>).
  4599   -->
  4600   <binding id="tabbrowser-close-tab-button"
  4601            extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-image">
  4602     <handlers>
  4603       <handler event="click" button="0"><![CDATA[
  4604         var bindingParent = document.getBindingParent(this);
  4605         var tabContainer = bindingParent.parentNode;
  4606         tabContainer.tabbrowser.removeTab(bindingParent, {animate: true, byMouse: true});
  4607         // This enables double-click protection for the tab container
  4608         // (see tabbrowser-tabs 'click' handler).
  4609         tabContainer._blockDblClick = true;
  4610       ]]></handler>
  4612       <handler event="dblclick" button="0" phase="capturing">
  4613         // for the one-close-button case
  4614         event.stopPropagation();
  4615       </handler>
  4617       <handler event="dragstart">
  4618         event.stopPropagation();
  4619       </handler>
  4620     </handlers>
  4621   </binding>
  4623   <binding id="tabbrowser-tab" display="xul:hbox"
  4624            extends="chrome://global/content/bindings/tabbox.xml#tab">
  4625     <resources>
  4626       <stylesheet src="chrome://browser/content/tabbrowser.css"/>
  4627     </resources>
  4629     <content context="tabContextMenu" closetabtext="&closeTab.label;">
  4630       <xul:stack class="tab-stack" flex="1">
  4631         <xul:hbox xbl:inherits="pinned,selected,titlechanged,fadein"
  4632                   class="tab-background">
  4633           <xul:hbox xbl:inherits="pinned,selected,titlechanged"
  4634                     class="tab-background-start"/>
  4635           <xul:hbox xbl:inherits="pinned,selected,titlechanged"
  4636                     class="tab-background-middle"/>
  4637           <xul:hbox xbl:inherits="pinned,selected,titlechanged"
  4638                     class="tab-background-end"/>
  4639         </xul:hbox>
  4640         <xul:hbox xbl:inherits="pinned,selected,titlechanged"
  4641                   class="tab-content" align="center">
  4642           <xul:image xbl:inherits="fadein,pinned,busy,progress,selected"
  4643                      class="tab-throbber"
  4644                      role="presentation"
  4645                      layer="true" />
  4646           <xul:image xbl:inherits="src=image,fadein,pinned,selected"
  4647                      anonid="tab-icon-image"
  4648                      class="tab-icon-image"
  4649                      validate="never"
  4650                      role="presentation"/>
  4651           <xul:label flex="1"
  4652                      anonid="tab-label"
  4653                      xbl:inherits="value=visibleLabel,crop,accesskey,fadein,pinned,selected"
  4654                      class="tab-text tab-label"
  4655                      role="presentation"/>
  4656           <xul:toolbarbutton anonid="close-button"
  4657                              xbl:inherits="fadein,pinned,selected"
  4658                              class="tab-close-button close-icon"/>
  4659         </xul:hbox>
  4660       </xul:stack>
  4661     </content>
  4663     <implementation>
  4664       <property name="label">
  4665         <getter>
  4666           return this.getAttribute("label");
  4667         </getter>
  4668         <setter>
  4669           this.setAttribute("label", val);
  4670           let event = new CustomEvent("TabLabelModified", {
  4671             bubbles: true,
  4672             cancelable: true
  4673           });
  4674           this.dispatchEvent(event);
  4676           // Let listeners prevent synchronizing the actual label to the
  4677           // visible label (allowing them to override the visible label).
  4678           if (!event.defaultPrevented)
  4679             this.visibleLabel = val;
  4680         </setter>
  4681       </property>
  4682       <property name="visibleLabel">
  4683         <getter>
  4684           return this.getAttribute("visibleLabel");
  4685         </getter>
  4686         <setter>
  4687           this.setAttribute("visibleLabel", val);
  4688         </setter>
  4689       </property>
  4690       <property name="pinned" readonly="true">
  4691         <getter>
  4692           return this.getAttribute("pinned") == "true";
  4693         </getter>
  4694       </property>
  4695       <property name="hidden" readonly="true">
  4696         <getter>
  4697           return this.getAttribute("hidden") == "true";
  4698         </getter>
  4699       </property>
  4701       <property name="lastAccessed">
  4702         <getter>
  4703           return this.selected ? Date.now() : this._lastAccessed;
  4704         </getter>
  4705         <setter>
  4706           this._lastAccessed = val;
  4707         </setter>
  4708       </property>
  4709       <field name="_lastAccessed">0</field>
  4711       <field name="mOverCloseButton">false</field>
  4712       <field name="mCorrespondingMenuitem">null</field>
  4713       <field name="closing">false</field>
  4715       <method name="_mouseenter">
  4716         <body><![CDATA[
  4717           if (this.hidden || this.closing)
  4718             return;
  4720           let tabContainer = this.parentNode;
  4721           let visibleTabs = tabContainer.tabbrowser.visibleTabs;
  4722           let tabIndex = visibleTabs.indexOf(this);
  4723           if (tabIndex == 0) {
  4724             tabContainer._beforeHoveredTab = null;
  4725           } else {
  4726             let candidate = visibleTabs[tabIndex - 1];
  4727             if (!candidate.selected) {
  4728               tabContainer._beforeHoveredTab = candidate;
  4729               candidate.setAttribute("beforehovered", "true");
  4733           if (tabIndex == visibleTabs.length - 1) {
  4734             tabContainer._afterHoveredTab = null;
  4735           } else {
  4736             let candidate = visibleTabs[tabIndex + 1];
  4737             if (!candidate.selected) {
  4738               tabContainer._afterHoveredTab = candidate;
  4739               candidate.setAttribute("afterhovered", "true");
  4743           tabContainer._hoveredTab = this;
  4744         ]]></body>
  4745       </method>
  4747       <method name="_mouseleave">
  4748         <body><![CDATA[
  4749           let tabContainer = this.parentNode;
  4750           if (tabContainer._beforeHoveredTab) {
  4751             tabContainer._beforeHoveredTab.removeAttribute("beforehovered");
  4752             tabContainer._beforeHoveredTab = null;
  4754           if (tabContainer._afterHoveredTab) {
  4755             tabContainer._afterHoveredTab.removeAttribute("afterhovered");
  4756             tabContainer._afterHoveredTab = null;
  4759           tabContainer._hoveredTab = null;
  4760         ]]></body>
  4761       </method>
  4762     </implementation>
  4764     <handlers>
  4765       <handler event="mouseover"><![CDATA[
  4766         let anonid = event.originalTarget.getAttribute("anonid");
  4767         if (anonid == "close-button")
  4768           this.mOverCloseButton = true;
  4770         this._mouseenter();
  4771       ]]></handler>
  4772       <handler event="mouseout"><![CDATA[
  4773         let anonid = event.originalTarget.getAttribute("anonid");
  4774         if (anonid == "close-button")
  4775           this.mOverCloseButton = false;
  4777         this._mouseleave();
  4778       ]]></handler>
  4779       <handler event="dragstart" phase="capturing">
  4780         this.style.MozUserFocus = '';
  4781       </handler>
  4782       <handler event="mousedown" phase="capturing">
  4783       <![CDATA[
  4784         if (this.selected) {
  4785           this.style.MozUserFocus = 'ignore';
  4786           this.clientTop; // just using this to flush style updates
  4787         } else if (this.mOverCloseButton) {
  4788           // Prevent tabbox.xml from selecting the tab.
  4789           event.stopPropagation();
  4791       ]]>
  4792       </handler>
  4793       <handler event="mouseup">
  4794         this.style.MozUserFocus = '';
  4795       </handler>
  4796     </handlers>
  4797   </binding>
  4799   <binding id="tabbrowser-alltabs-popup"
  4800            extends="chrome://global/content/bindings/popup.xml#popup">
  4801     <implementation implements="nsIDOMEventListener">
  4802       <method name="_tabOnAttrModified">
  4803         <parameter name="aEvent"/>
  4804         <body><![CDATA[
  4805           var tab = aEvent.target;
  4806           if (tab.mCorrespondingMenuitem)
  4807             this._setMenuitemAttributes(tab.mCorrespondingMenuitem, tab);
  4808         ]]></body>
  4809       </method>
  4811       <method name="_tabOnTabClose">
  4812         <parameter name="aEvent"/>
  4813         <body><![CDATA[
  4814           var tab = aEvent.target;
  4815           if (tab.mCorrespondingMenuitem)
  4816             this.removeChild(tab.mCorrespondingMenuitem);
  4817         ]]></body>
  4818       </method>
  4820       <method name="handleEvent">
  4821         <parameter name="aEvent"/>
  4822         <body><![CDATA[
  4823           switch (aEvent.type) {
  4824             case "TabAttrModified":
  4825               this._tabOnAttrModified(aEvent);
  4826               break;
  4827             case "TabClose":
  4828               this._tabOnTabClose(aEvent);
  4829               break;
  4830             case "scroll":
  4831               this._updateTabsVisibilityStatus();
  4832               break;
  4834         ]]></body>
  4835       </method>
  4837       <method name="_updateTabsVisibilityStatus">
  4838         <body><![CDATA[
  4839           var tabContainer = gBrowser.tabContainer;
  4840           // We don't want menu item decoration unless there is overflow.
  4841           if (tabContainer.getAttribute("overflow") != "true")
  4842             return;
  4844           var tabstripBO = tabContainer.mTabstrip.scrollBoxObject;
  4845           for (var i = 0; i < this.childNodes.length; i++) {
  4846             let curTab = this.childNodes[i].tab;
  4847             if (!curTab) // "Tab Groups" menuitem and its menuseparator
  4848               continue;
  4849             let curTabBO = curTab.boxObject;
  4850             if (curTabBO.screenX >= tabstripBO.screenX &&
  4851                 curTabBO.screenX + curTabBO.width <= tabstripBO.screenX + tabstripBO.width)
  4852               this.childNodes[i].setAttribute("tabIsVisible", "true");
  4853             else
  4854               this.childNodes[i].removeAttribute("tabIsVisible");
  4856         ]]></body>
  4857       </method>
  4859       <method name="_createTabMenuItem">
  4860         <parameter name="aTab"/>
  4861         <body><![CDATA[
  4862           var menuItem = document.createElementNS(
  4863             "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
  4864             "menuitem");
  4866           menuItem.setAttribute("class", "menuitem-iconic alltabs-item menuitem-with-favicon");
  4868           this._setMenuitemAttributes(menuItem, aTab);
  4870           aTab.mCorrespondingMenuitem = menuItem;
  4871           menuItem.tab = aTab;
  4873           this.appendChild(menuItem);
  4874         ]]></body>
  4875       </method>
  4877       <method name="_setMenuitemAttributes">
  4878         <parameter name="aMenuitem"/>
  4879         <parameter name="aTab"/>
  4880         <body><![CDATA[
  4881           aMenuitem.setAttribute("label", aTab.label);
  4882           aMenuitem.setAttribute("crop", aTab.getAttribute("crop"));
  4884           if (aTab.hasAttribute("busy")) {
  4885             aMenuitem.setAttribute("busy", aTab.getAttribute("busy"));
  4886             aMenuitem.removeAttribute("image");
  4887           } else {
  4888             aMenuitem.setAttribute("image", aTab.getAttribute("image"));
  4889             aMenuitem.removeAttribute("busy");
  4892           if (aTab.hasAttribute("pending"))
  4893             aMenuitem.setAttribute("pending", aTab.getAttribute("pending"));
  4894           else
  4895             aMenuitem.removeAttribute("pending");
  4897           if (aTab.selected)
  4898             aMenuitem.setAttribute("selected", "true");
  4899           else
  4900             aMenuitem.removeAttribute("selected");
  4901         ]]></body>
  4902       </method>
  4903     </implementation>
  4905     <handlers>
  4906       <handler event="popupshowing">
  4907       <![CDATA[
  4908         var tabcontainer = gBrowser.tabContainer;
  4910         // Listen for changes in the tab bar.
  4911         tabcontainer.addEventListener("TabAttrModified", this, false);
  4912         tabcontainer.addEventListener("TabClose", this, false);
  4913         tabcontainer.mTabstrip.addEventListener("scroll", this, false);
  4915         let tabs = gBrowser.visibleTabs;
  4916         for (var i = 0; i < tabs.length; i++) {
  4917           if (!tabs[i].pinned)
  4918             this._createTabMenuItem(tabs[i]);
  4920         this._updateTabsVisibilityStatus();
  4921       ]]></handler>
  4923       <handler event="popuphidden">
  4924       <![CDATA[
  4925         // clear out the menu popup and remove the listeners
  4926         for (let i = this.childNodes.length - 1; i > 0; i--) {
  4927           let menuItem = this.childNodes[i];
  4928           if (menuItem.tab) {
  4929             menuItem.tab.mCorrespondingMenuitem = null;
  4930             this.removeChild(menuItem);
  4933         var tabcontainer = gBrowser.tabContainer;
  4934         tabcontainer.mTabstrip.removeEventListener("scroll", this, false);
  4935         tabcontainer.removeEventListener("TabAttrModified", this, false);
  4936         tabcontainer.removeEventListener("TabClose", this, false);
  4937       ]]></handler>
  4939       <handler event="DOMMenuItemActive">
  4940       <![CDATA[
  4941         var tab = event.target.tab;
  4942         if (tab) {
  4943           let overLink = tab.linkedBrowser.currentURI.spec;
  4944           if (overLink == "about:blank")
  4945             overLink = "";
  4946           XULBrowserWindow.setOverLink(overLink, null);
  4948       ]]></handler>
  4950       <handler event="DOMMenuItemInactive">
  4951       <![CDATA[
  4952         XULBrowserWindow.setOverLink("", null);
  4953       ]]></handler>
  4955       <handler event="command"><![CDATA[
  4956         if (event.target.tab)
  4957           gBrowser.selectedTab = event.target.tab;
  4958       ]]></handler>
  4960     </handlers>
  4961   </binding>
  4963   <binding id="statuspanel" display="xul:hbox">
  4964     <content>
  4965       <xul:hbox class="statuspanel-inner">
  4966         <xul:label class="statuspanel-label"
  4967                    role="status"
  4968                    aria-live="off"
  4969                    xbl:inherits="value=label,crop,mirror"
  4970                    flex="1"
  4971                    crop="end"/>
  4972       </xul:hbox>
  4973     </content>
  4975     <implementation implements="nsIDOMEventListener">
  4976       <constructor><![CDATA[
  4977         window.addEventListener("resize", this, false);
  4978       ]]></constructor>
  4980       <destructor><![CDATA[
  4981         window.removeEventListener("resize", this, false);
  4982         MousePosTracker.removeListener(this);
  4983       ]]></destructor>
  4985       <property name="label">
  4986         <setter><![CDATA[
  4987           if (!this.label) {
  4988             this.removeAttribute("mirror");
  4989             this.removeAttribute("sizelimit");
  4992           this.style.minWidth = this.getAttribute("type") == "status" &&
  4993                                 this.getAttribute("previoustype") == "status"
  4994                                   ? getComputedStyle(this).width : "";
  4996           if (val) {
  4997             this.setAttribute("label", val);
  4998             this.removeAttribute("inactive");
  4999             this._calcMouseTargetRect();
  5000             MousePosTracker.addListener(this);
  5001           } else {
  5002             this.setAttribute("inactive", "true");
  5003             MousePosTracker.removeListener(this);
  5006           return val;
  5007         ]]></setter>
  5008         <getter>
  5009           return this.hasAttribute("inactive") ? "" : this.getAttribute("label");
  5010         </getter>
  5011       </property>
  5013       <method name="getMouseTargetRect">
  5014         <body><![CDATA[
  5015           return this._mouseTargetRect;
  5016         ]]></body>
  5017       </method>
  5019       <method name="onMouseEnter">
  5020         <body>
  5021           this._mirror();
  5022         </body>
  5023       </method>
  5025       <method name="onMouseLeave">
  5026         <body>
  5027           this._mirror();
  5028         </body>
  5029       </method>
  5031       <method name="handleEvent">
  5032         <parameter name="event"/>
  5033         <body><![CDATA[
  5034           if (!this.label)
  5035             return;
  5037           switch (event.type) {
  5038             case "resize":
  5039               this._calcMouseTargetRect();
  5040               break;
  5042         ]]></body>
  5043       </method>
  5045       <method name="_calcMouseTargetRect">
  5046         <body><![CDATA[
  5047           let container = this.parentNode;
  5048           let alignRight = (getComputedStyle(container).direction == "rtl");
  5049           let panelRect = this.getBoundingClientRect();
  5050           let containerRect = container.getBoundingClientRect();
  5052           this._mouseTargetRect = {
  5053             top:    panelRect.top,
  5054             bottom: panelRect.bottom,
  5055             left:   alignRight ? containerRect.right - panelRect.width : containerRect.left,
  5056             right:  alignRight ? containerRect.right : containerRect.left + panelRect.width
  5057           };
  5058         ]]></body>
  5059       </method>
  5061       <method name="_mirror">
  5062         <body>
  5063           if (this.hasAttribute("mirror"))
  5064             this.removeAttribute("mirror");
  5065           else
  5066             this.setAttribute("mirror", "true");
  5068           if (!this.hasAttribute("sizelimit")) {
  5069             this.setAttribute("sizelimit", "true");
  5070             this._calcMouseTargetRect();
  5072         </body>
  5073       </method>
  5074     </implementation>
  5075   </binding>
  5077 </bindings>

mercurial