Bug 608589 - Avoid false positives when complaining about tabs that failed to close...
[mozilla-central.git] / browser / base / content / tabbrowser.xml
blobe995702841fb628f1aa69658e85ce0e4d575740c
1 <?xml version="1.0"?>
3 <!-- ***** BEGIN LICENSE BLOCK *****
4    - Version: MPL 1.1/GPL 2.0/LGPL 2.1
5    -
6    - The contents of this file are subject to the Mozilla Public License Version
7    - 1.1 (the "License"); you may not use this file except in compliance with
8    - the License. You may obtain a copy of the License at
9    - http://www.mozilla.org/MPL/
10    -
11    - Software distributed under the License is distributed on an "AS IS" basis,
12    - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13    - for the specific language governing rights and limitations under the
14    - License.
15    -
16    - The Original Code is this file as it was released on March 28, 2001.
17    -
18    - The Initial Developer of the Original Code is
19    - David Hyatt.
20    - Portions created by the Initial Developer are Copyright (C) 2001
21    - the Initial Developer. All Rights Reserved.
22    -
23    - Contributor(s):
24    -   David Hyatt <hyatt@netscape.com> (Original Author of <tabbrowser>)
25    -   Mike Connor <mconnor@steelgryphon.com>
26    -   Peter Parente <parente@cs.unc.edu>
27    -   Giorgio Maone <g.maone@informaction.com>
28    -   Asaf Romano <mozilla.mano@sent.com>
29    -   Seth Spitzer <sspitzer@mozilla.org>
30    -   Simon Bünzli <zeniko@gmail.com>
31    -   Michael Ventnor <ventnor.bugzilla@yahoo.com.au>
32    -   Mark Pilgrim <pilgrim@gmail.com>
33    -   Dão Gottwald <dao@mozilla.com>
34    -   Paul O’Shannessy <paul@oshannessy.com>
35    -   Rob Arnold <tellrob@gmail.com>
36    -
37    - Alternatively, the contents of this file may be used under the terms of
38    - either the GNU General Public License Version 2 or later (the "GPL"), or
39    - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
40    - in which case the provisions of the GPL or the LGPL are applicable instead
41    - of those above. If you wish to allow use of your version of this file only
42    - under the terms of either the GPL or the LGPL, and not to allow others to
43    - use your version of this file under the terms of the MPL, indicate your
44    - decision by deleting the provisions above and replace them with the notice
45    - and other provisions required by the GPL or the LGPL. If you do not delete
46    - the provisions above, a recipient may use your version of this file under
47    - the terms of any one of the MPL, the GPL or the LGPL.
48    -
49    - ***** END LICENSE BLOCK ***** -->
51 <!DOCTYPE bindings [
52 <!ENTITY % tabBrowserDTD SYSTEM "chrome://browser/locale/tabbrowser.dtd" >
53 %tabBrowserDTD;
56 <bindings id="tabBrowserBindings"
57           xmlns="http://www.mozilla.org/xbl"
58           xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
59           xmlns:xbl="http://www.mozilla.org/xbl">
61   <binding id="tabbrowser">
62     <resources>
63       <stylesheet src="chrome://browser/content/tabbrowser.css"/>
64     </resources>
66     <content>
67       <xul:stringbundle anonid="tbstringbundle" src="chrome://browser/locale/tabbrowser.properties"/>
68       <xul:tabbox anonid="tabbox" class="tabbrowser-tabbox"
69                   flex="1" eventnode="document" xbl:inherits="handleCtrlPageUpDown"
70                   onselect="if (event.target.localName == 'tabpanels') this.parentNode.updateCurrentBrowser();">
71         <xul:tabpanels flex="1" class="plain" selectedIndex="0" anonid="panelcontainer">
72           <xul:notificationbox flex="1">
73             <xul:stack flex="1" anonid="browserStack">
74               <xul:browser type="content-primary" message="true" disablehistory="true"
75                            xbl:inherits="tooltip=contenttooltip,contextmenu=contentcontextmenu,autocompletepopup"/>
76             </xul:stack>
77           </xul:notificationbox>
78         </xul:tabpanels>
79       </xul:tabbox>
80       <children/>
81     </content>
82     <implementation implements="nsIDOMEventListener">
84       <property name="tabContextMenu" readonly="true"
85                 onget="return this.tabContainer.contextMenu;"/>
87       <field name="tabContainer" readonly="true">
88         document.getElementById(this.getAttribute("tabcontainer"));
89       </field>
90       <field name="tabs" readonly="true">
91         this.tabContainer.childNodes;
92       </field>
93       <property name="visibleTabs" readonly="true">
94         <getter><![CDATA[
95           return Array.filter(this.tabs, function(tab) {
96             return !tab.hidden && this._removingTabs.indexOf(tab) == -1;
97           }, this);
98         ]]></getter>
99       </property>
100       <field name="mURIFixup" readonly="true">
101         Components.classes["@mozilla.org/docshell/urifixup;1"]
102                   .getService(Components.interfaces.nsIURIFixup);
103       </field>
104       <field name="mFaviconService" readonly="true">
105         Components.classes["@mozilla.org/browser/favicon-service;1"]
106                   .getService(Components.interfaces.nsIFaviconService);
107       </field>
108       <field name="_placesAutocomplete" readonly="true">
109          Components.classes["@mozilla.org/autocomplete/search;1?name=history"]
110                    .getService(Components.interfaces.mozIPlacesAutoComplete);
111       </field>
112       <field name="mTabBox" readonly="true">
113         document.getAnonymousElementByAttribute(this, "anonid", "tabbox");
114       </field>
115       <field name="mPanelContainer" readonly="true">
116         document.getAnonymousElementByAttribute(this, "anonid", "panelcontainer");
117       </field>
118       <field name="mStringBundle">
119         document.getAnonymousElementByAttribute(this, "anonid", "tbstringbundle");
120       </field>
121       <field name="mCurrentTab">
122         null
123       </field>
124       <field name="_lastRelatedTab">
125         null
126       </field>
127       <field name="mCurrentBrowser">
128         null
129       </field>
130       <field name="mProgressListeners">
131         []
132       </field>
133       <field name="mTabsProgressListeners">
134         []
135       </field>
136       <field name="mTabListeners">
137         new Array()
138       </field>
139       <field name="mTabFilters">
140         new Array()
141       </field>
142       <field name="mTabbedMode">
143         false
144       </field>
145       <field name="mIsBusy">
146         false
147       </field>
148       <field name="arrowKeysShouldWrap" readonly="true">
149 #ifdef XP_MACOSX
150         true
151 #else
152         false
153 #endif
154       </field>
155       <field name="mAddProgressListenerWasCalled">
156         false
157       </field>
158       <field name="_browsers">
159         null
160       </field>
162       <field name="_autoScrollPopup">
163         null
164       </field>
166       <field name="_previewMode">
167         false
168       </field>
170       <property name="_numPinnedTabs" readonly="true">
171         <getter><![CDATA[
172           for (var i = 0; i < this.tabs.length; i++) {
173             if (!this.tabs[i].pinned)
174               break;
175           }
176           return i;
177         ]]></getter>
178       </property>
180       <method name="pinTab">
181         <parameter name="aTab"/>
182         <body><![CDATA[
183           if (aTab.pinned)
184             return;
186           if (aTab.hidden)
187             this.showTab(aTab);
189           this.moveTabTo(aTab, this._numPinnedTabs);
190           aTab.setAttribute("pinned", "true");
191           this.tabContainer._positionPinnedTabs();
192           this.tabContainer.adjustTabstrip();
194           this.getBrowserForTab(aTab).docShell.isAppTab = true;
196           let event = document.createEvent("Events");
197           event.initEvent("TabPinned", true, false);
198           aTab.dispatchEvent(event);
199         ]]></body>
200       </method>
202       <method name="unpinTab">
203         <parameter name="aTab"/>
204         <body><![CDATA[
205           if (!aTab.pinned)
206             return;
208           this.moveTabTo(aTab, this._numPinnedTabs - 1);
209           aTab.setAttribute("fadein", "true");
210           aTab.removeAttribute("pinned");
211           aTab.style.MozMarginStart = "";
212           this.tabContainer._positionPinnedTabs();
213           this.tabContainer.adjustTabstrip();
215           this.getBrowserForTab(aTab).docShell.isAppTab = false;
217           let event = document.createEvent("Events");
218           event.initEvent("TabUnpinned", true, false);
219           aTab.dispatchEvent(event);
220         ]]></body>
221       </method>
223       <method name="previewTab">
224         <parameter name="aTab"/>
225         <parameter name="aCallback"/>
226         <body>
227           <![CDATA[
228             let currentTab = this.selectedTab;
229             try {
230               // Suppress focus, ownership and selected tab changes
231               this._previewMode = true;
232               this.selectedTab = aTab;
233               aCallback();
234             } finally {
235               this.selectedTab = currentTab;
236               this._previewMode = false;
237             }
238           ]]>
239         </body>
240       </method>
242       <method name="getBrowserAtIndex">
243         <parameter name="aIndex"/>
244         <body>
245           <![CDATA[
246             return this.browsers[aIndex];
247           ]]>
248         </body>
249       </method>
251       <method name="getBrowserIndexForDocument">
252         <parameter name="aDocument"/>
253         <body>
254           <![CDATA[
255             var tab = this._getTabForContentWindow(aDocument.defaultView);
256             return tab ? tab._tPos : -1;
257           ]]>
258         </body>
259       </method>
261       <method name="getBrowserForDocument">
262         <parameter name="aDocument"/>
263         <body>
264           <![CDATA[
265             var tab = this._getTabForContentWindow(aDocument.defaultView);
266             return tab ? tab.linkedBrowser : null;
267           ]]>
268         </body>
269       </method>
271       <method name="_getTabForContentWindow">
272         <parameter name="aWindow"/>
273         <body>
274         <![CDATA[
275           for (let i = 0; i < this.browsers.length; i++) {
276             if (this.browsers[i].contentWindow == aWindow)
277               return this.tabs[i];
278           }
279           return null;
280         ]]>
281         </body>
282       </method>
284       <method name="getNotificationBox">
285         <parameter name="aBrowser"/>
286         <body>
287           <![CDATA[
288             return (aBrowser || this.mCurrentBrowser).parentNode.parentNode;
289           ]]>
290         </body>
291       </method>
293       <method name="getTabModalPromptBox">
294         <parameter name="aBrowser"/>
295         <body>
296           <![CDATA[
297             const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
298             let browser = (aBrowser || this.mCurrentBrowser);
299             let stack = browser.parentNode;
300             let self = this;
302             let promptBox = {
303               appendPrompt : function(args, onCloseCallback) {
304                 let count = browser.getAttribute("tabmodalPromptShowing");
305                 if (count)
306                     count = parseInt(count) + 1;
307                 else
308                     count = 1;
309                 browser.setAttribute("tabmodalPromptShowing", count);
311                 let newPrompt = document.createElementNS(XUL_NS, "tabmodalprompt");
312                 stack.appendChild(newPrompt);
313                 newPrompt.clientTop; // style flush to assure binding is attached
315                 let tab = self._getTabForContentWindow(browser.contentWindow);
316                 newPrompt.init(args, tab, onCloseCallback);
317                 return newPrompt;
318               },
320               removePrompt : function(aPrompt) {
321                 let count = parseInt(browser.getAttribute("tabmodalPromptShowing"));
322                 count--;
323                 if (count)
324                     browser.setAttribute("tabmodalPromptShowing", count);
325                 else
326                     browser.removeAttribute("tabmodalPromptShowing");
327                 stack.removeChild(aPrompt);
328               },
330               listPrompts : function(aPrompt) {
331                 let prompts = [];
332                 let els = stack.getElementsByTagNameNS(XUL_NS, "tabmodalprompt");
333                 // NodeList --> real JS array
334                 for (let i = 0; i < els.length; i++)
335                   prompts.push(els[i]);
336                 return prompts;
337               },
338             };
340             return promptBox;
341           ]]>
342         </body>
343       </method>
345       <method name="_callProgressListeners">
346         <parameter name="aBrowser"/>
347         <parameter name="aMethod"/>
348         <parameter name="aArguments"/>
349         <parameter name="aCallGlobalListeners"/>
350         <parameter name="aCallTabsListeners"/>
351         <body><![CDATA[
352           var rv = true;
354           if (!aBrowser)
355             aBrowser = this.mCurrentBrowser;
357           if (aCallGlobalListeners != false &&
358               aBrowser == this.mCurrentBrowser) {
359             this.mProgressListeners.forEach(function (p) {
360               if (aMethod in p) {
361                 try {
362                   if (!p[aMethod].apply(p, aArguments))
363                     rv = false;
364                 } catch (e) {
365                   // don't inhibit other listeners
366                   Components.utils.reportError(e);
367                 }
368               }
369             });
370           }
372           if (aCallTabsListeners != false) {
373             aArguments.unshift(aBrowser);
375             this.mTabsProgressListeners.forEach(function (p) {
376               if (aMethod in p) {
377                 try {
378                   if (!p[aMethod].apply(p, aArguments))
379                     rv = false;
380                 } catch (e) {
381                   // don't inhibit other listeners
382                   Components.utils.reportError(e);
383                 }
384               }
385             });
386           }
388           return rv;
389         ]]></body>
390       </method>
392       <!-- A web progress listener object definition for a given tab. -->
393       <method name="mTabProgressListener">
394         <parameter name="aTab"/>
395         <parameter name="aBrowser"/>
396         <parameter name="aStartsBlank"/>
397         <body>
398         <![CDATA[
399           return ({
400             mTabBrowser: this,
401             mTab: aTab,
402             mBrowser: aBrowser,
403             mBlank: aStartsBlank,
405             // cache flags for correct status UI update after tab switching
406             mStateFlags: 0,
407             mStatus: 0,
408             mMessage: "",
409             mTotalProgress: 0,
411             // count of open requests (should always be 0 or 1)
412             mRequestCount: 0,
414             destroy: function () {
415               delete this.mTab;
416               delete this.mBrowser;
417               delete this.mTabBrowser;
418             },
420             _callProgressListeners: function () {
421               Array.unshift(arguments, this.mBrowser);
422               return this.mTabBrowser._callProgressListeners.apply(this.mTabBrowser, arguments);
423             },
425             onProgressChange: function (aWebProgress, aRequest,
426                                         aCurSelfProgress, aMaxSelfProgress,
427                                         aCurTotalProgress, aMaxTotalProgress) {
428               this.mTotalProgress = aMaxTotalProgress ? aCurTotalProgress / aMaxTotalProgress : 0;
430               if (this.mBlank)
431                 return;
433               if (this.mTotalProgress)
434                 this.mTab.setAttribute("progress", "true");
436               this._callProgressListeners("onProgressChange",
437                                           [aWebProgress, aRequest,
438                                            aCurSelfProgress, aMaxSelfProgress,
439                                            aCurTotalProgress, aMaxTotalProgress]);
440             },
442             onProgressChange64: function (aWebProgress, aRequest,
443                                           aCurSelfProgress, aMaxSelfProgress,
444                                           aCurTotalProgress, aMaxTotalProgress) {
445               return this.onProgressChange(aWebProgress, aRequest,
446                 aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress,
447                 aMaxTotalProgress);
448             },
450             onStateChange: function (aWebProgress, aRequest, aStateFlags, aStatus) {
451               if (!aRequest)
452                 return;
454               var oldBlank = this.mBlank;
456               const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
457               const nsIChannel = Components.interfaces.nsIChannel;
459               if (aStateFlags & nsIWebProgressListener.STATE_START) {
460                 this.mRequestCount++;
461               }
462               else if (aStateFlags & nsIWebProgressListener.STATE_STOP) {
463                 const NS_ERROR_UNKNOWN_HOST = 2152398878;
464                 if (--this.mRequestCount > 0 && aStatus == NS_ERROR_UNKNOWN_HOST) {
465                   // to prevent bug 235825: wait for the request handled
466                   // by the automatic keyword resolver
467                   return;
468                 }
469                 // since we (try to) only handle STATE_STOP of the last request,
470                 // the count of open requests should now be 0
471                 this.mRequestCount = 0;
472               }
474               if (aStateFlags & nsIWebProgressListener.STATE_START &&
475                   aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
476                 // It's okay to clear what the user typed when we start
477                 // loading a document. If the user types, this counter gets
478                 // set to zero, if the document load ends without an
479                 // onLocationChange, this counter gets decremented
480                 // (so we keep it while switching tabs after failed loads)
481                 // We need to add 2 because loadURIWithFlags may have
482                 // cancelled a pending load which would have cleared
483                 // its anchor scroll detection temporary increment.
484                 if (aWebProgress.DOMWindow == this.mBrowser.contentWindow)
485                   this.mBrowser.userTypedClear += 2;
487                 if (!this.mBlank) {
488                   if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) {
489                     this.mTab.setAttribute("busy", "true");
490                     this.mTabBrowser.setTabTitleLoading(this.mTab);
491                   }
493                   if (this.mTab.selected)
494                     this.mTabBrowser.mIsBusy = true;
495                 }
496               }
497               else if (aStateFlags & nsIWebProgressListener.STATE_STOP &&
498                        aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
499                 if (aWebProgress.DOMWindow == this.mBrowser.contentWindow) {
500                   // The document is done loading, we no longer want the
501                   // value cleared.
502                   if (this.mBrowser.userTypedClear > 1)
503                     this.mBrowser.userTypedClear -= 2;
504                   else if (this.mBrowser.userTypedClear > 0)
505                     this.mBrowser.userTypedClear--;
507                   if (!this.mBrowser.mIconURL)
508                     this.mTabBrowser.useDefaultIcon(this.mTab);
509                 }
511                 if (this.mBlank)
512                   this.mBlank = false;
514                 this.mTab.removeAttribute("busy");
515                 this.mTab.removeAttribute("progress");
517                 var location = aRequest.QueryInterface(nsIChannel).URI;
519                 // For keyword URIs clear the user typed value since they will be changed into real URIs
520                 if (location.scheme == "keyword")
521                   this.mBrowser.userTypedValue = null;
523                 if (this.mTab.label == this.mTabBrowser.mStringBundle.getString("tabs.connecting"))
524                   this.mTabBrowser.setTabTitle(this.mTab);
526                 if (this.mTab.selected)
527                   this.mTabBrowser.mIsBusy = false;
528               }
530               if (oldBlank) {
531                 this._callProgressListeners("onUpdateCurrentBrowser",
532                                             [aStateFlags, aStatus, "", 0],
533                                             true, false);
534               } else {
535                 this._callProgressListeners("onStateChange",
536                                             [aWebProgress, aRequest, aStateFlags, aStatus],
537                                             true, false);
538               }
540               this._callProgressListeners("onStateChange",
541                                           [aWebProgress, aRequest, aStateFlags, aStatus],
542                                           false);
544               if (aStateFlags & (nsIWebProgressListener.STATE_START |
545                                  nsIWebProgressListener.STATE_STOP)) {
546                 // reset cached temporary values at beginning and end
547                 this.mMessage = "";
548                 this.mTotalProgress = 0;
549               }
550               this.mStateFlags = aStateFlags;
551               this.mStatus = aStatus;
552             },
554             onLocationChange: function (aWebProgress, aRequest, aLocation) {
555               // OnLocationChange is called for both the top-level content
556               // and the subframes.
557               let topLevel = aWebProgress.DOMWindow == this.mBrowser.contentWindow;
559               if (topLevel) {
560                 // The document loaded correctly, clear the value if we should
561                 if (this.mBrowser.userTypedClear > 0)
562                   this.mBrowser.userTypedValue = null;
564                 // Clear out the missing plugins list since it's related to the
565                 // previous location.
566                 this.mBrowser.missingPlugins = null;
568                 // Don't clear the favicon if this onLocationChange was
569                 // triggered by a pushState or a replaceState.  See bug 550565.
570                 if (aWebProgress.isLoadingDocument &&
571                     !(this.mBrowser.docShell.loadType & Ci.nsIDocShell.LOAD_CMD_PUSHSTATE))
572                   this.mBrowser.mIconURL = null;
574                 let autocomplete = this.mTabBrowser._placesAutocomplete;
575                 if (this.mBrowser.registeredOpenURI) {
576                   autocomplete.unregisterOpenPage(this.mBrowser.registeredOpenURI);
577                   delete this.mBrowser.registeredOpenURI;
578                 }
579                 if (aLocation.spec != "about:blank") {
580                   autocomplete.registerOpenPage(aLocation);
581                   this.mBrowser.registeredOpenURI = aLocation;
582                 }
583               }
585               if (!this.mBlank) {
586                 this._callProgressListeners("onLocationChange",
587                                             [aWebProgress, aRequest, aLocation]);
588               }
590               if (topLevel)
591                 this.mBrowser.lastURI = aLocation;
592             },
594             onStatusChange: function (aWebProgress, aRequest, aStatus, aMessage) {
595               if (this.mBlank)
596                 return;
598               this._callProgressListeners("onStatusChange",
599                                           [aWebProgress, aRequest, aStatus, aMessage]);
601               this.mMessage = aMessage;
602             },
604             onSecurityChange: function (aWebProgress, aRequest, aState) {
605               this._callProgressListeners("onSecurityChange",
606                                           [aWebProgress, aRequest, aState]);
607             },
609             onRefreshAttempted: function (aWebProgress, aURI, aDelay, aSameURI) {
610               return this._callProgressListeners("onRefreshAttempted",
611                                                  [aWebProgress, aURI, aDelay, aSameURI]);
612             },
614             QueryInterface: function (aIID) {
615               if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
616                   aIID.equals(Components.interfaces.nsIWebProgressListener2) ||
617                   aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
618                   aIID.equals(Components.interfaces.nsISupports))
619                 return this;
620               throw Components.results.NS_NOINTERFACE;
621             }
622           });
623         ]]>
624         </body>
625       </method>
627       <method name="setIcon">
628         <parameter name="aTab"/>
629         <parameter name="aURI"/>
630         <body>
631           <![CDATA[
632             var browser = this.getBrowserForTab(aTab);
633             browser.mIconURL = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
635             if (aURI && this.mFaviconService) {
636               if (!(aURI instanceof Ci.nsIURI))
637                 aURI = makeURI(aURI);
638               this.mFaviconService.setAndLoadFaviconForPage(browser.currentURI,
639                                                             aURI, false);
640             }
642             if ((browser.mIconURL || "") != aTab.getAttribute("image")) {
643               if (browser.mIconURL)
644                 aTab.setAttribute("image", browser.mIconURL);
645               else
646                 aTab.removeAttribute("image");
647               this._tabAttrModified(aTab);
648             }
650             this._callProgressListeners(browser, "onLinkIconAvailable", [browser.mIconURL]);
651           ]]>
652         </body>
653       </method>
655       <method name="getIcon">
656         <parameter name="aTab"/>
657         <body>
658           <![CDATA[
659             let browser = aTab ? this.getBrowserForTab(aTab) : this.selectedBrowser;
660             return browser.mIconURL;
661           ]]>
662         </body>
663       </method>
665       <method name="shouldLoadFavIcon">
666         <parameter name="aURI"/>
667         <body>
668           <![CDATA[
669             return (aURI &&
670                     Services.prefs.getBoolPref("browser.chrome.site_icons") &&
671                     Services.prefs.getBoolPref("browser.chrome.favicons") &&
672                     ("schemeIs" in aURI) && (aURI.schemeIs("http") || aURI.schemeIs("https")));
673           ]]>
674         </body>
675       </method>
677       <method name="useDefaultIcon">
678         <parameter name="aTab"/>
679         <body>
680           <![CDATA[
681             var browser = this.getBrowserForTab(aTab);
682             var docURIObject = browser.contentDocument.documentURIObject;
683             var icon = null;
684             if (browser.contentDocument instanceof ImageDocument) {
685               if (Services.prefs.getBoolPref("browser.chrome.site_icons")) {
686                 let sz = Services.prefs.getIntPref("browser.chrome.image_icons.max_size");
687                 try {
688                   let req = browser.contentDocument.imageRequest;
689                   if (req &&
690                       req.image &&
691                       req.image.width <= sz &&
692                       req.image.height <= sz)
693                     icon = browser.currentURI;
694                 } catch (e) { }
695               }
696             }
697             // Use documentURIObject in the check for shouldLoadFavIcon so that we
698             // do the right thing with about:-style error pages.  Bug 453442
699             else if (this.shouldLoadFavIcon(docURIObject)) {
700               let url = docURIObject.prePath + "/favicon.ico";
701               if (!this.isFailedIcon(url))
702                 icon = url;
703             }
704             this.setIcon(aTab, icon);
705           ]]>
706         </body>
707       </method>
709       <method name="isFailedIcon">
710         <parameter name="aURI"/>
711         <body>
712           <![CDATA[
713             if (this.mFaviconService) {
714               if (!(aURI instanceof Ci.nsIURI))
715                 aURI = makeURI(aURI);
716               return this.mFaviconService.isFailedFavicon(aURI);
717             }
718             return null;
719           ]]>
720         </body>
721       </method>
723       <method name="getWindowTitleForBrowser">
724         <parameter name="aBrowser"/>
725         <body>
726           <![CDATA[
727             var newTitle = "";
728             var docTitle;
729             var docElement = this.ownerDocument.documentElement;
730             var sep = docElement.getAttribute("titlemenuseparator");
732             if (aBrowser.docShell.contentViewer)
733               docTitle = aBrowser.contentTitle;
735             if (!docTitle)
736               docTitle = docElement.getAttribute("titledefault");
738             var modifier = docElement.getAttribute("titlemodifier");
739             if (docTitle) {
740               newTitle += docElement.getAttribute("titlepreface");
741               newTitle += docTitle;
742               if (modifier)
743                 newTitle += sep;
744             }
745             newTitle += modifier;
747             // If location bar is hidden and the URL type supports a host,
748             // add the scheme and host to the title to prevent spoofing.
749             // XXX https://bugzilla.mozilla.org/show_bug.cgi?id=22183#c239
750             try {
751               if (docElement.getAttribute("chromehidden").indexOf("location") != -1) {
752                 var uri = this.mURIFixup.createExposableURI(
753                             aBrowser.currentURI);
754                 if (uri.scheme == "about")
755                   newTitle = uri.spec + sep + newTitle;
756                 else
757                   newTitle = uri.prePath + sep + newTitle;
758               }
759             } catch (e) {}
761             if (window.TabView) {
762               let groupName = TabView.getActiveGroupName();
763               if (groupName)
764                 newTitle = groupName + sep + newTitle;
765             }
767             return newTitle;
768           ]]>
769         </body>
770       </method>
772       <method name="updateTitlebar">
773         <body>
774           <![CDATA[
775             if (window.TabView && TabView.isVisible()) {
776               // ToDo: this will be removed when we gain ability to draw to the menu bar.
777               // Bug 586175
778               this.ownerDocument.title = TabView.windowTitle;
779             }
780             else {
781               this.ownerDocument.title = this.getWindowTitleForBrowser(this.mCurrentBrowser);
782             }
783           ]]>
784         </body>
785       </method>
787       <method name="updateCurrentBrowser">
788         <parameter name="aForceUpdate"/>
789         <body>
790           <![CDATA[
791             var newBrowser = this.getBrowserAtIndex(this.tabContainer.selectedIndex);
792             if (this.mCurrentBrowser == newBrowser && !aForceUpdate)
793               return;
795             var oldTab = this.mCurrentTab;
797             // Preview mode should not reset the owner
798             if (!this._previewMode && oldTab != this.selectedTab)
799               oldTab.owner = null;
801             if (this._lastRelatedTab) {
802               if (this._lastRelatedTab != this.selectedTab)
803                 this._lastRelatedTab.owner = null;
804               this._lastRelatedTab = null;
805             }
807             var oldBrowser = this.mCurrentBrowser;
808             if (oldBrowser) {
809               oldBrowser.setAttribute("type", "content-targetable");
810               oldBrowser.docShell.isActive = false;
811             }
813             var updatePageReport = false;
814             if (!oldBrowser ||
815                 (oldBrowser.pageReport && !newBrowser.pageReport) ||
816                 (!oldBrowser.pageReport && newBrowser.pageReport))
817               updatePageReport = true;
819             newBrowser.setAttribute("type", "content-primary");
820             newBrowser.docShell.isActive = true;
821             this.mCurrentBrowser = newBrowser;
822             this.mCurrentTab = this.selectedTab;
823             this.showTab(this.mCurrentTab);
825             if (updatePageReport)
826               this.mCurrentBrowser.updatePageReport();
828             // Update the URL bar.
829             var loc = this.mCurrentBrowser.currentURI;
831             var webProgress = this.mCurrentBrowser.webProgress;
832             var securityUI = this.mCurrentBrowser.securityUI;
834             this._callProgressListeners(null, "onLocationChange",
835                                         [webProgress, null, loc], true, false);
837             if (securityUI) {
838               this._callProgressListeners(null, "onSecurityChange",
839                                           [webProgress, null, securityUI.state], true, false);
840             }
842             var listener = this.mTabListeners[this.tabContainer.selectedIndex] || null;
843             if (listener && listener.mStateFlags) {
844               this._callProgressListeners(null, "onUpdateCurrentBrowser",
845                                           [listener.mStateFlags, listener.mStatus,
846                                            listener.mMessage, listener.mTotalProgress],
847                                           true, false);
848             }
850             // Don't switch the fast find or update the titlebar (bug 540248) - this tab switch is temporary
851             if (!this._previewMode) {
852               this._fastFind.setDocShell(this.mCurrentBrowser.docShell);
854               this.updateTitlebar();
856               this.mCurrentTab.removeAttribute("titlechanged");
857             }
859             // If the new tab is busy, and our current state is not busy, then
860             // we need to fire a start to all progress listeners.
861             const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
862             if (this.mCurrentTab.hasAttribute("busy") && !this.mIsBusy) {
863               this.mIsBusy = true;
864               this._callProgressListeners(null, "onStateChange",
865                                           [webProgress, null,
866                                            nsIWebProgressListener.STATE_START |
867                                            nsIWebProgressListener.STATE_IS_NETWORK, 0],
868                                           true, false);
869             }
871             // If the new tab is not busy, and our current state is busy, then
872             // we need to fire a stop to all progress listeners.
873             if (!this.mCurrentTab.hasAttribute("busy") && this.mIsBusy) {
874               this.mIsBusy = false;
875               this._callProgressListeners(null, "onStateChange",
876                                           [webProgress, null,
877                                            nsIWebProgressListener.STATE_STOP |
878                                            nsIWebProgressListener.STATE_IS_NETWORK, 0],
879                                           true, false);
880             }
882             // TabSelect events are suppressed during preview mode to avoid confusing extensions and other bits of code
883             // that might rely upon the other changes suppressed.
884             // Focus is suppressed in the event that the main browser window is minimized - focusing a tab would restore the window
885             if (!this._previewMode) {
886               // We've selected the new tab, so go ahead and notify listeners.
887               let event = document.createEvent("Events");
888               event.initEvent("TabSelect", true, false);
889               this.mCurrentTab.dispatchEvent(event);
891               this._tabAttrModified(oldTab);
892               this._tabAttrModified(this.mCurrentTab);
894               // Adjust focus
895               do {
896                 // Focus the location bar if it was previously focused for that tab.
897                 // In full screen mode, only bother making the location bar visible
898                 // if the tab is a blank one.
899                 oldBrowser._urlbarFocused = (gURLBar && gURLBar.focused);
900                 if (newBrowser._urlbarFocused && gURLBar) {
901                   if (!window.fullScreen) {
902                     gURLBar.focus();
903                     break;
904                   } else if (isTabEmpty(this.mCurrentTab)) {
905                     focusAndSelectUrlBar();
906                     break;
907                   }
908                 }
910                 // If the find bar is focused, keep it focused.
911                 if (gFindBarInitialized &&
912                     !gFindBar.hidden &&
913                     gFindBar.getElement("findbar-textbox").getAttribute("focused") == "true")
914                   break;
916                 // Otherwise, focus the content area.
917                 let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
918                 let newFocusedElement = fm.getFocusedElementForWindow(window.content, true, {});
920                 // for anchors, use FLAG_SHOWRING so that it is clear what link was
921                 // last clicked when switching back to that tab
922                 let focusFlags = fm.FLAG_NOSCROLL;
923                 if (newFocusedElement &&
924                     (newFocusedElement instanceof HTMLAnchorElement ||
925                      newFocusedElement.getAttributeNS("http://www.w3.org/1999/xlink", "type") == "simple"))
926                   focusFlags |= fm.FLAG_SHOWRING;
927                 fm.setFocus(newBrowser, focusFlags);
928               } while (false);
929             }
930           ]]>
931         </body>
932       </method>
934       <method name="_tabAttrModified">
935         <parameter name="aTab"/>
936         <body><![CDATA[
937           if (this._removingTabs.indexOf(aTab) > -1)
938             return;
940           // This event should be dispatched when any of these attributes change:
941           // label, crop, busy, image, selected
942           var event = document.createEvent("Events");
943           event.initEvent("TabAttrModified", true, false);
944           aTab.dispatchEvent(event);
945         ]]></body>
946       </method>
948       <method name="setTabTitleLoading">
949         <parameter name="aTab"/>
950         <body>
951           <![CDATA[
952             aTab.label = this.mStringBundle.getString("tabs.connecting");
953             aTab.crop = "end";
954             this._tabAttrModified(aTab);
955           ]]>
956         </body>
957       </method>
959       <method name="setTabTitle">
960         <parameter name="aTab"/>
961         <body>
962           <![CDATA[
963             var browser = this.getBrowserForTab(aTab);
964             var crop = "end";
965             var title = browser.contentTitle;
967             if (!title) {
968               if (browser.currentURI.spec) {
969                 try {
970                   title = this.mURIFixup.createExposableURI(browser.currentURI).spec;
971                 } catch(ex) {
972                   title = browser.currentURI.spec;
973                 }
974               }
976               if (title && title != "about:blank") {
977                 // At this point, we now have a URI.
978                 // Let's try to unescape it using a character set
979                 // in case the URI is not ASCII.
980                 try {
981                   var characterSet = browser.contentDocument.characterSet;
982                   const textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"]
983                                                  .getService(Components.interfaces.nsITextToSubURI);
984                   title = textToSubURI.unEscapeNonAsciiURI(characterSet, title);
985                 } catch(ex) { /* Do nothing. */ }
987                 crop = "center";
989               } else // Still no title?  Fall back to our untitled string.
990                 title = this.mStringBundle.getString("tabs.emptyTabTitle");
991             }
993             if (aTab.label == title &&
994                 aTab.crop == crop)
995               return false;
997             aTab.label = title;
998             aTab.crop = crop;
999             this._tabAttrModified(aTab);
1001             if (aTab.selected)
1002               this.updateTitlebar();
1004             return true;
1005           ]]>
1006         </body>
1007       </method>
1009       <method name="enterTabbedMode">
1010         <body>
1011           <![CDATA[
1012             if (this.mTabbedMode)
1013               return;
1015             this.mTabbedMode = true; // Welcome to multi-tabbed mode.
1017             if (XULBrowserWindow.isBusy) {
1018               this.mCurrentTab.setAttribute("busy", "true");
1019               this.mIsBusy = true;
1020               this.setTabTitleLoading(this.mCurrentTab);
1021             } else {
1022               this.setIcon(this.mCurrentTab, this.mCurrentBrowser.mIconURL);
1023             }
1025             var filter;
1026             if (this.mTabFilters.length > 0) {
1027               // Use the filter hooked up in our addProgressListener
1028               filter = this.mTabFilters[0];
1029             } else {
1030               // create a filter and hook it up to our first browser
1031               filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
1032                                  .createInstance(Components.interfaces.nsIWebProgress);
1033               this.mTabFilters[0] = filter;
1034               this.mCurrentBrowser.webProgress.addProgressListener(filter, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
1035             }
1037             // Remove all our progress listeners from the active browser's filter.
1038             this.mProgressListeners.forEach(filter.removeProgressListener, filter);
1040             // Wire up a progress listener to our filter.
1041             const listener = this.mTabProgressListener(this.mCurrentTab,
1042                                                        this.mCurrentBrowser,
1043                                                        false);
1044             filter.addProgressListener(listener, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
1045             this.mTabListeners[0] = listener;
1046           ]]>
1047         </body>
1048       </method>
1050       <method name="loadOneTab">
1051         <parameter name="aURI"/>
1052         <parameter name="aReferrerURI"/>
1053         <parameter name="aCharset"/>
1054         <parameter name="aPostData"/>
1055         <parameter name="aLoadInBackground"/>
1056         <parameter name="aAllowThirdPartyFixup"/>
1057         <body>
1058           <![CDATA[
1059             var aFromExternal;
1060             var aRelatedToCurrent;
1061             if (arguments.length == 2 &&
1062                 typeof arguments[1] == "object" &&
1063                 !(arguments[1] instanceof Ci.nsIURI)) {
1064               let params = arguments[1];
1065               aReferrerURI          = params.referrerURI;
1066               aCharset              = params.charset;
1067               aPostData             = params.postData;
1068               aLoadInBackground     = params.inBackground;
1069               aAllowThirdPartyFixup = params.allowThirdPartyFixup;
1070               aFromExternal         = params.fromExternal;
1071               aRelatedToCurrent     = params.relatedToCurrent;
1072             }
1074             var bgLoad = (aLoadInBackground != null) ? aLoadInBackground :
1075                          Services.prefs.getBoolPref("browser.tabs.loadInBackground");
1076             var owner = bgLoad ? null : this.selectedTab;
1077             var tab = this.addTab(aURI, {
1078                                   referrerURI: aReferrerURI,
1079                                   charset: aCharset,
1080                                   postData: aPostData,
1081                                   ownerTab: owner,
1082                                   allowThirdPartyFixup: aAllowThirdPartyFixup,
1083                                   fromExternal: aFromExternal,
1084                                   relatedToCurrent: aRelatedToCurrent});
1085             if (!bgLoad)
1086               this.selectedTab = tab;
1088             return tab;
1089          ]]>
1090         </body>
1091       </method>
1093       <method name="loadTabs">
1094         <parameter name="aURIs"/>
1095         <parameter name="aLoadInBackground"/>
1096         <parameter name="aReplace"/>
1097         <body><![CDATA[
1098           if (!aURIs.length)
1099             return;
1101           // The tab selected after this new tab is closed (i.e. the new tab's
1102           // "owner") is the next adjacent tab (i.e. not the previously viewed tab)
1103           // when several urls are opened here (i.e. closing the first should select
1104           // the next of many URLs opened) or if the pref to have UI links opened in
1105           // the background is set (i.e. the link is not being opened modally)
1106           //
1107           // i.e.
1108           //    Number of URLs    Load UI Links in BG       Focus Last Viewed?
1109           //    == 1              false                     YES
1110           //    == 1              true                      NO
1111           //    > 1               false/true                NO
1112           var multiple = aURIs.length > 1;
1113           var owner = multiple || aLoadInBackground ? null : this.selectedTab;
1114           var firstTabAdded = null;
1116           if (aReplace) {
1117             try {
1118               this.loadURI(aURIs[0], null, null);
1119             } catch (e) {
1120               // Ignore failure in case a URI is wrong, so we can continue
1121               // opening the next ones.
1122             }
1123           }
1124           else
1125             firstTabAdded = this.addTab(aURIs[0], {ownerTab: owner, skipAnimation: multiple});
1127           var tabNum = this.tabContainer.selectedIndex;
1128           for (let i = 1; i < aURIs.length; ++i) {
1129             let tab = this.addTab(aURIs[i], {skipAnimation: true});
1130             if (aReplace)
1131               this.moveTabTo(tab, ++tabNum);
1132           }
1134           if (!aLoadInBackground) {
1135             if (firstTabAdded) {
1136               // .selectedTab setter focuses the content area
1137               this.selectedTab = firstTabAdded;
1138             }
1139             else
1140               window.content.focus();
1141           }
1142         ]]></body>
1143       </method>
1145       <method name="addTab">
1146         <parameter name="aURI"/>
1147         <parameter name="aReferrerURI"/>
1148         <parameter name="aCharset"/>
1149         <parameter name="aPostData"/>
1150         <parameter name="aOwner"/>
1151         <parameter name="aAllowThirdPartyFixup"/>
1152         <body>
1153           <![CDATA[
1154             var aFromExternal;
1155             var aRelatedToCurrent;
1156             var aSkipAnimation;
1157             if (arguments.length == 2 &&
1158                 typeof arguments[1] == "object" &&
1159                 !(arguments[1] instanceof Ci.nsIURI)) {
1160               let params = arguments[1];
1161               aReferrerURI          = params.referrerURI;
1162               aCharset              = params.charset;
1163               aPostData             = params.postData;
1164               aOwner                = params.ownerTab;
1165               aAllowThirdPartyFixup = params.allowThirdPartyFixup;
1166               aFromExternal         = params.fromExternal;
1167               aRelatedToCurrent     = params.relatedToCurrent;
1168               aSkipAnimation        = params.skipAnimation;
1169             }
1171             this._browsers = null; // invalidate cache
1173             this.enterTabbedMode();
1175             // if we're adding tabs, we're past interrupt mode, ditch the owner
1176             if (this.mCurrentTab.owner)
1177               this.mCurrentTab.owner = null;
1179             var t = document.createElementNS(
1180               "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
1181                                              "tab");
1183             var blank = !aURI || (aURI == "about:blank");
1185             if (blank)
1186               t.setAttribute("label", this.mStringBundle.getString("tabs.emptyTabTitle"));
1187             else
1188               t.setAttribute("label", aURI);
1190             t.setAttribute("crop", "end");
1191             t.setAttribute("validate", "never");
1192             t.setAttribute("onerror", "this.removeAttribute('image');");
1193             t.className = "tabbrowser-tab";
1195             // When overflowing, new tabs are scrolled into view smoothly, which
1196             // doesn't go well together with the width transition. So we skip the
1197             // transition in that case.
1198             if (aSkipAnimation ||
1199                 this.tabContainer.getAttribute("overflow") == "true" ||
1200                 !Services.prefs.getBoolPref("browser.tabs.animate")) {
1201               t.setAttribute("fadein", "true");
1202               setTimeout(function (tabContainer) {
1203                 tabContainer._handleNewTab(t);
1204               }, 0, this.tabContainer);
1205             } else {
1206               setTimeout(function (tabContainer) {
1207                 if (t.pinned)
1208                   tabContainer._handleNewTab(t);
1209                 else
1210                   t.setAttribute("fadein", "true");
1211               }, 0, this.tabContainer);
1212             }
1214             this.tabContainer.appendChild(t);
1215             if (this.tabContainer.getAttribute("pinnedonly") == "true")
1216               this.tabContainer._positionPinnedTabs();
1218             if (this.tabContainer.mTabstrip._isRTLScrollbox) {
1219               /* In RTL UI, the tab is visually added to the left side of the
1220                * tabstrip. This means the tabstip has to be scrolled back in
1221                * order to make sure the same set of tabs is visible before and
1222                * after the new tab is added.  See bug 508816. */
1224               this.tabContainer.mTabstrip.scrollByPixels(t.clientWidth);
1225             }
1227             // invalidate cache, because tabContainer is about to change
1228             this._browsers = null;
1230             // If this new tab is owned by another, assert that relationship
1231             if (aOwner)
1232               t.owner = aOwner;
1234             var b = document.createElementNS(
1235               "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
1236                                              "browser");
1237             b.setAttribute("type", "content-targetable");
1238             b.setAttribute("message", "true");
1239             b.setAttribute("contextmenu", this.getAttribute("contentcontextmenu"));
1240             b.setAttribute("tooltip", this.getAttribute("contenttooltip"));
1241             if (this.hasAttribute("autocompletepopup"))
1242               b.setAttribute("autocompletepopup", this.getAttribute("autocompletepopup"));
1243             b.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
1245             // Create the browserStack container
1246             var stack = document.createElementNS(
1247                                     "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
1248                                     "stack");
1249             stack.setAttribute("anonid", "browserStack");
1250             stack.appendChild(b);
1251             stack.setAttribute("flex", "1");
1253             // Add the Message and the Browser to the box
1254             var notificationbox = document.createElementNS(
1255                                     "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
1256                                     "notificationbox");
1257             notificationbox.setAttribute("flex", "1");
1258             notificationbox.appendChild(stack);
1260             var position = this.tabs.length - 1;
1261             var uniqueId = "panel" + Date.now() + position;
1262             notificationbox.id = uniqueId;
1263             t.linkedPanel = uniqueId;
1264             t.linkedBrowser = b;
1265             t._tPos = position;
1266             if (t.previousSibling.selected)
1267               t.setAttribute("afterselected", true);
1269             // NB: this appendChild call causes us to run constructors for the
1270             // browser element, which fires off a bunch of notifications. Some
1271             // of those notifications can cause code to run that inspects our
1272             // state, so it is important that the tab element is fully
1273             // initialized by this point.
1274             this.mPanelContainer.appendChild(notificationbox);
1276             this.tabContainer.updateVisibility();
1278             // wire up a progress listener for the new browser object.
1279             var tabListener = this.mTabProgressListener(t, b, blank);
1280             const filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
1281                                      .createInstance(Components.interfaces.nsIWebProgress);
1282             filter.addProgressListener(tabListener, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
1283             b.webProgress.addProgressListener(filter, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
1284             this.mTabListeners[position] = tabListener;
1285             this.mTabFilters[position] = filter;
1287             b._fastFind = this.fastFind;
1288             b.droppedLinkHandler = handleDroppedLink;
1290             // Dispatch a new tab notification.  We do this once we're
1291             // entirely done, so that things are in a consistent state
1292             // even if the event listener opens or closes tabs.
1293             var evt = document.createEvent("Events");
1294             evt.initEvent("TabOpen", true, false);
1295             t.dispatchEvent(evt);
1297             if (!blank) {
1298               // Stop the existing about:blank load.  Otherwise, if aURI
1299               // doesn't stop in-progress loads on its own, we'll get into
1300               // trouble with multiple parallel loads running at once.
1301               b.stop();
1303               // pretend the user typed this so it'll be available till
1304               // the document successfully loads
1305               b.userTypedValue = aURI;
1307               let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
1308               if (aAllowThirdPartyFixup)
1309                 flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
1310               if (aFromExternal)
1311                 flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL;
1312               try {
1313                 b.loadURIWithFlags(aURI, flags, aReferrerURI, aCharset, aPostData);
1315               }
1316               catch (ex) { }
1317             }
1319             // We start our browsers out as inactive, and then maintain
1320             // activeness in the tab switcher.
1321             b.docShell.isActive = false;
1323             // Check if we're opening a tab related to the current tab and
1324             // move it to after the current tab.
1325             // aReferrerURI is null or undefined if the tab is opened from
1326             // an external application or bookmark, i.e. somewhere other
1327             // than the current tab.
1328             if ((aRelatedToCurrent == null ? aReferrerURI : aRelatedToCurrent) &&
1329                 Services.prefs.getBoolPref("browser.tabs.insertRelatedAfterCurrent")) {
1330               let newTabPos = (this._lastRelatedTab ||
1331                                this.selectedTab)._tPos + 1;
1332               if (this._lastRelatedTab)
1333                 this._lastRelatedTab.owner = null;
1334               else
1335                 t.owner = this.selectedTab;
1336               this.moveTabTo(t, newTabPos);
1337               this._lastRelatedTab = t;
1338             }
1340             return t;
1341           ]]>
1342         </body>
1343       </method>
1345       <method name="warnAboutClosingTabs">
1346       <parameter name="aAll"/>
1347       <body>
1348         <![CDATA[
1349           var tabsToClose = (aAll ? this.tabs.length : this.visibleTabs.length - 1)
1350                             - gBrowser._numPinnedTabs;
1351           if (tabsToClose <= 1)
1352             return true;
1354           const pref = "browser.tabs.warnOnClose";
1355           var shouldPrompt = Services.prefs.getBoolPref(pref);
1357           if (!shouldPrompt)
1358             return true;
1360           var ps = Services.prompt;
1362           // default to true: if it were false, we wouldn't get this far
1363           var warnOnClose = { value: true };
1364           var bundle = this.mStringBundle;
1366           // focus the window before prompting.
1367           // this will raise any minimized window, which will
1368           // make it obvious which window the prompt is for and will
1369           // solve the problem of windows "obscuring" the prompt.
1370           // see bug #350299 for more details
1371           window.focus();
1372           var buttonPressed =
1373             ps.confirmEx(window,
1374                          bundle.getString("tabs.closeWarningTitle"),
1375                          bundle.getFormattedString("tabs.closeWarningMultipleTabs",
1376                                                    [tabsToClose]),
1377                          (ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_0)
1378                          + (ps.BUTTON_TITLE_CANCEL * ps.BUTTON_POS_1),
1379                          bundle.getString("tabs.closeButtonMultiple"),
1380                          null, null,
1381                          bundle.getString('tabs.closeWarningPromptMe'),
1382                          warnOnClose);
1383           var reallyClose = (buttonPressed == 0);
1384           // don't set the pref unless they press OK and it's false
1385           if (reallyClose && !warnOnClose.value)
1386             Services.prefs.setBoolPref(pref, false);
1388           return reallyClose;
1389         ]]>
1390       </body>
1391       </method>
1393       <method name="removeAllTabsBut">
1394         <parameter name="aTab"/>
1395         <body>
1396           <![CDATA[
1397             if (aTab.pinned)
1398               return;
1400             if (this.warnAboutClosingTabs(false)) {
1401               let tabs = this.visibleTabs;
1402               this.selectedTab = aTab;
1404               for (let i = tabs.length - 1; i >= 0; --i) {
1405                 if (tabs[i] != aTab && !tabs[i].pinned)
1406                   this.removeTab(tabs[i]);
1407               }
1408             }
1409           ]]>
1410         </body>
1411       </method>
1413       <method name="removeCurrentTab">
1414         <parameter name="aParams"/>
1415         <body>
1416           <![CDATA[
1417             this.removeTab(this.mCurrentTab, aParams);
1418           ]]>
1419         </body>
1420       </method>
1422       <field name="_removingTabs">
1423         []
1424       </field>
1426       <method name="removeTab">
1427         <parameter name="aTab"/>
1428         <parameter name="aParams"/>
1429         <body>
1430           <![CDATA[
1431             if (aParams)
1432               var animate = aParams.animate;
1434             // Handle requests for synchronously removing an already
1435             // asynchronously closing tab.
1436             if (!animate &&
1437                 this._removingTabs.indexOf(aTab) > -1) {
1438               this._endRemoveTab(aTab);
1439               return;
1440             }
1442             var isLastTab = (this.tabs.length - this._removingTabs.length == 1);
1444             if (!this._beginRemoveTab(aTab, false, null, true))
1445               return;
1447             if (!animate /* the caller didn't opt in */ ||
1448                 isLastTab ||
1449                 aTab.pinned ||
1450                 this._removingTabs.length > 3 /* don't want lots of concurrent animations */ ||
1451                 aTab.getAttribute("fadein") != "true" /* fade-in transition hasn't been triggered yet */ ||
1452                 window.getComputedStyle(aTab).maxWidth == "0.1px" /* fade-in transition hasn't moved yet */ ||
1453                 !Services.prefs.getBoolPref("browser.tabs.animate")) {
1454               this._endRemoveTab(aTab);
1455               return;
1456             }
1458             this._blurTab(aTab);
1459             aTab.removeAttribute("fadein");
1461             setTimeout(function (tab, tabbrowser) {
1462               if (tab.parentNode &&
1463                   window.getComputedStyle(tab).maxWidth == "0.1px") {
1464                 NS_ASSERT(false, "Giving up waiting for the tab closing animation to finish (bug 608589)");
1465                 tabbrowser._endRemoveTab(tab);
1466               }
1467             }, 3000, aTab, this);
1468           ]]>
1469         </body>
1470       </method>
1472       <!-- Tab close requests are ignored if the window is closing anyway,
1473            e.g. when holding Ctrl+W. -->
1474       <field name="_windowIsClosing">
1475         false
1476       </field>
1478       <method name="_beginRemoveTab">
1479         <parameter name="aTab"/>
1480         <parameter name="aTabWillBeMoved"/>
1481         <parameter name="aCloseWindowWithLastTab"/>
1482         <parameter name="aCloseWindowFastpath"/>
1483         <body>
1484           <![CDATA[
1485             if (this._removingTabs.indexOf(aTab) > -1 || this._windowIsClosing)
1486               return false;
1488             var browser = this.getBrowserForTab(aTab);
1490             if (!aTabWillBeMoved) {
1491               let ds = browser.docShell;
1492               if (ds && ds.contentViewer && !ds.contentViewer.permitUnload())
1493                 return false;
1494             }
1496             var closeWindow = false;
1497             var newTab = false;
1498             if (this.tabs.length - this._removingTabs.length == 1) {
1499               closeWindow = aCloseWindowWithLastTab != null ? aCloseWindowWithLastTab :
1500                             !window.toolbar.visible ||
1501                               this.tabContainer._closeWindowWithLastTab;
1503               // Closing the tab and replacing it with a blank one is notably slower
1504               // than closing the window right away. If the caller opts in, take
1505               // the fast path.
1506               if (closeWindow &&
1507                   aCloseWindowFastpath &&
1508                   this._removingTabs.length == 0 &&
1509                   (this._windowIsClosing = window.closeWindow(true)))
1510                 return null;
1512               newTab = true;
1513             }
1515             this._removingTabs.push(aTab);
1517             this.tabContainer.updateVisibility();
1519             // We're committed to closing the tab now.
1520             // Dispatch a notification.
1521             // We dispatch it before any teardown so that event listeners can
1522             // inspect the tab that's about to close.
1523             var evt = document.createEvent("UIEvent");
1524             evt.initUIEvent("TabClose", true, false, window, aTabWillBeMoved ? 1 : 0);
1525             aTab.dispatchEvent(evt);
1527             // Remove the tab's filter and progress listener.
1528             const filter = this.mTabFilters[aTab._tPos];
1529             browser.webProgress.removeProgressListener(filter);
1530             filter.removeProgressListener(this.mTabListeners[aTab._tPos]);
1531             this.mTabListeners[aTab._tPos].destroy();
1533             if (browser.registeredOpenURI && !aTabWillBeMoved) {
1534               this._placesAutocomplete.unregisterOpenPage(browser.registeredOpenURI);
1535               delete browser.registeredOpenURI;
1536             }
1538             // We are no longer the primary content area.
1539             browser.setAttribute("type", "content-targetable");
1541             // Remove this tab as the owner of any other tabs, since it's going away.
1542             Array.forEach(this.tabs, function (tab) {
1543               if ("owner" in tab && tab.owner == aTab)
1544                 // |tab| is a child of the tab we're removing, make it an orphan
1545                 tab.owner = null;
1546             });
1548             aTab._endRemoveArgs = [closeWindow, newTab];
1549             return true;
1550           ]]>
1551         </body>
1552       </method>
1554       <method name="_endRemoveTab">
1555         <parameter name="aTab"/>
1556         <body>
1557           <![CDATA[
1558             if (!aTab || !aTab._endRemoveArgs)
1559               return;
1561             var [aCloseWindow, aNewTab] = aTab._endRemoveArgs;
1562             aTab._endRemoveArgs = null;
1564             if (this._windowIsClosing) {
1565               aCloseWindow = false;
1566               aNewTab = false;
1567             }
1569             this._lastRelatedTab = null;
1571             // update the UI early for responsiveness
1572             aTab.collapsed = true;
1573             if (aNewTab)
1574               this.addTab("about:blank", {skipAnimation: true});
1575             this.tabContainer._fillTrailingGap();
1576             this._blurTab(aTab);
1578             this._removingTabs.splice(this._removingTabs.indexOf(aTab), 1);
1580             if (aCloseWindow) {
1581               this._windowIsClosing = true;
1582               while (this._removingTabs.length)
1583                 this._endRemoveTab(this._removingTabs[0]);
1584             } else if (!this._windowIsClosing) {
1585               if (aNewTab)
1586                 focusAndSelectUrlBar();
1588               // workaround for bug 345399
1589               this.tabContainer.mTabstrip._updateScrollButtonsDisabledState();
1590             }
1592             // We're going to remove the tab and the browser now.
1593             // Clean up mTabFilters and mTabListeners now rather than in
1594             // _beginRemoveTab, so that their size is always in sync with the
1595             // number of tabs and browsers (the xbl destructor depends on this).
1596             this.mTabFilters.splice(aTab._tPos, 1);
1597             this.mTabListeners.splice(aTab._tPos, 1);
1599             var browser = this.getBrowserForTab(aTab);
1601             // Because of the way XBL works (fields just set JS
1602             // properties on the element) and the code we have in place
1603             // to preserve the JS objects for any elements that have
1604             // JS properties set on them, the browser element won't be
1605             // destroyed until the document goes away.  So we force a
1606             // cleanup ourselves.
1607             // This has to happen before we remove the child so that the
1608             // XBL implementation of nsIObserver still works.
1609             browser.destroy();
1611             if (browser == this.mCurrentBrowser)
1612               this.mCurrentBrowser = null;
1614             // Invalidate browsers cache, as the tab is removed from the
1615             // tab container.
1616             this._browsers = null;
1618             // Remove the tab ...
1619             this.tabContainer.removeChild(aTab);
1621             // ... and fix up the _tPos properties immediately.
1622             for (let i = aTab._tPos; i < this.tabs.length; i++)
1623               this.tabs[i]._tPos = i;
1625             if (!this._windowIsClosing) {
1626               this.tabContainer._positionPinnedTabs();
1628               // update tab close buttons state
1629               this.tabContainer.adjustTabstrip();
1630             }
1632             // update first-tab/last-tab/beforeselected/afterselected attributes
1633             this.selectedTab._selected = true;
1635             // Removing the panel requires fixing up selectedPanel immediately
1636             // (see below), which would be hindered by the potentially expensive
1637             // browser removal. So we remove the browser and the panel in two
1638             // steps.
1639             var panel = browser.parentNode.parentNode;
1641             // This will unload the document. An unload handler could remove
1642             // dependant tabs, so it's important that the tabbrowser is now in
1643             // a consistent state (tab removed, tab positions updated, etc.).
1644             panel.removeChild(browser.parentNode);
1646             // As the browser is removed, the removal of a dependent document can
1647             // cause the whole window to close. So at this point, it's possible
1648             // that the binding is destructed.
1649             if (this.mTabBox) {
1650               let selectedPanel = this.mTabBox.selectedPanel;
1652               this.mPanelContainer.removeChild(panel);
1654               // Under the hood, a selectedIndex attribute controls which panel
1655               // is displayed. Removing a panel A which precedes the selected
1656               // panel B makes selectedIndex point to the panel next to B. We
1657               // need to explicitly preserve B as the selected panel.
1658               this.mTabBox.selectedPanel = selectedPanel;
1659             }
1661             if (aCloseWindow)
1662               this._windowIsClosing = closeWindow(true);
1663           ]]>
1664         </body>
1665       </method>
1667       <method name="_blurTab">
1668         <parameter name="aTab"/>
1669         <body>
1670           <![CDATA[
1671             if (this.mCurrentTab != aTab)
1672               return;
1674             if (aTab.owner &&
1675                 this._removingTabs.indexOf(aTab.owner) == -1 &&
1676                 Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose")) {
1677               this.selectedTab = aTab.owner;
1678               return;
1679             }
1681             // Switch to a visible tab unless there aren't any others remaining
1682             let remainingTabs = this.visibleTabs;
1683             let numTabs = remainingTabs.length;
1684             if (numTabs == 0 || numTabs == 1 && remainingTabs[0] == aTab) {
1685               remainingTabs = Array.filter(this.tabs, function(tab) {
1686                 return this._removingTabs.indexOf(tab) == -1;
1687               }, this);
1688             }
1690             // Try to find a remaining tab that comes after the given tab
1691             var tab = aTab;
1692             do {
1693               tab = tab.nextSibling;
1694             } while (tab && remainingTabs.indexOf(tab) == -1);
1696             if (!tab) {
1697               tab = aTab;
1699               do {
1700                 tab = tab.previousSibling;
1701               } while (tab && remainingTabs.indexOf(tab) == -1);
1702             }
1704             this.selectedTab = tab;
1705           ]]>
1706         </body>
1707       </method>
1709       <method name="swapBrowsersAndCloseOther">
1710         <parameter name="aOurTab"/>
1711         <parameter name="aOtherTab"/>
1712         <body>
1713           <![CDATA[
1714             // That's gBrowser for the other window, not the tab's browser!
1715             var remoteBrowser = aOtherTab.ownerDocument.defaultView.gBrowser;
1717             // First, start teardown of the other browser.  Make sure to not
1718             // fire the beforeunload event in the process.  Close the other
1719             // window if this was its last tab.
1720             if (!remoteBrowser._beginRemoveTab(aOtherTab, true, true))
1721               return;
1723             // Unhook our progress listener
1724             var ourIndex = aOurTab._tPos;
1725             const filter = this.mTabFilters[ourIndex];
1726             var tabListener = this.mTabListeners[ourIndex];
1727             var ourBrowser = this.getBrowserForTab(aOurTab);
1728             ourBrowser.webProgress.removeProgressListener(filter);
1729             filter.removeProgressListener(tabListener);
1730             var tabListenerBlank = tabListener.mBlank;
1732             var otherBrowser = aOtherTab.linkedBrowser;
1734             // Restore current registered open URI.
1735             if (ourBrowser.registeredOpenURI) {
1736               this._placesAutocomplete.unregisterOpenPage(ourBrowser.registeredOpenURI);
1737               delete ourBrowser.registeredOpenURI;
1738             }
1739             if (otherBrowser.registeredOpenURI) {
1740               ourBrowser.registeredOpenURI = otherBrowser.registeredOpenURI;
1741               delete otherBrowser.registeredOpenURI;
1742             }
1744             // Workarounds for bug 458697
1745             // Icon might have been set on DOMLinkAdded, don't override that.
1746             if (!ourBrowser.mIconURL && otherBrowser.mIconURL)
1747               this.setIcon(aOurTab, otherBrowser.mIconURL);
1748             var isBusy = aOtherTab.hasAttribute("busy");
1749             if (isBusy) {
1750               aOurTab.setAttribute("busy", "true");
1751               this._tabAttrModified(aOurTab);
1752               if (aOurTab == this.selectedTab)
1753                 this.mIsBusy = true;
1754             }
1756             // Swap the docshells
1757             ourBrowser.swapDocShells(otherBrowser);
1759             // Finish tearing down the tab that's going away.
1760             remoteBrowser._endRemoveTab(aOtherTab);
1762             // Restore the progress listener
1763             tabListener = this.mTabProgressListener(aOurTab, ourBrowser,
1764                                                     tabListenerBlank);
1765             this.mTabListeners[ourIndex] = tabListener;
1766             filter.addProgressListener(tabListener,
1767               Components.interfaces.nsIWebProgress.NOTIFY_ALL);
1769             ourBrowser.webProgress.addProgressListener(filter,
1770               Components.interfaces.nsIWebProgress.NOTIFY_ALL);
1772             if (isBusy)
1773               this.setTabTitleLoading(aOurTab);
1774             else
1775               this.setTabTitle(aOurTab);
1777             // If the tab was already selected (this happpens in the scenario
1778             // of replaceTabWithWindow), notify onLocationChange, etc.
1779             if (aOurTab == this.selectedTab)
1780               this.updateCurrentBrowser(true);
1781           ]]>
1782         </body>
1783       </method>
1785       <method name="reloadAllTabs">
1786         <body>
1787           <![CDATA[
1788             let tabs = this.visibleTabs;
1789             let l = tabs.length;
1790             for (var i = 0; i < l; i++) {
1791               try {
1792                 this.getBrowserForTab(tabs[i]).reload();
1793               } catch (e) {
1794                 // ignore failure to reload so others will be reloaded
1795               }
1796             }
1797           ]]>
1798         </body>
1799       </method>
1801       <method name="reloadTab">
1802         <parameter name="aTab"/>
1803         <body>
1804           <![CDATA[
1805             this.getBrowserForTab(aTab).reload();
1806           ]]>
1807         </body>
1808       </method>
1810       <method name="addProgressListener">
1811         <parameter name="aListener"/>
1812         <parameter name="aMask"/>
1813         <body>
1814           <![CDATA[
1815             if (!this.mAddProgressListenerWasCalled) {
1816               this.mAddProgressListenerWasCalled = true;
1817               this.tabContainer.updateVisibility();
1818             }
1820             if (this.mProgressListeners.length == 1) {
1821               // If we are adding a 2nd progress listener, we need to enter tabbed mode
1822               // because the browser status filter can only handle one progress listener.
1823               // In tabbed mode, mTabProgressListener is used which will iterate over all listeners.
1824               this.enterTabbedMode();
1825             }
1827             this.mProgressListeners.push(aListener);
1829             if (!this.mTabbedMode) {
1830               // If someone does this:
1831               // addProgressListener, removeProgressListener, addProgressListener
1832               // don't create a new filter; reuse the existing filter.
1833               if (this.mTabFilters.length == 0) {
1834                 // hook a filter up to our first browser
1835                 const filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
1836                                          .createInstance(Components.interfaces.nsIWebProgress);
1837                 this.mTabFilters[0] = filter;
1838                 this.mCurrentBrowser.webProgress.addProgressListener(filter, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
1839               }
1841               // Directly hook the listener up to the filter for better performance
1842               this.mTabFilters[0].addProgressListener(aListener, aMask);
1843             }
1844           ]]>
1845         </body>
1846       </method>
1848       <method name="removeProgressListener">
1849         <parameter name="aListener"/>
1850         <body>
1851           <![CDATA[
1852             this.mProgressListeners =
1853               this.mProgressListeners.filter(function (l) l != aListener);
1855             if (!this.mTabbedMode)
1856               // Don't forget to remove it from the filter we hooked it up to
1857               this.mTabFilters[0].removeProgressListener(aListener);
1858          ]]>
1859         </body>
1860       </method>
1862       <method name="addTabsProgressListener">
1863         <parameter name="aListener"/>
1864         <body>
1865           this.enterTabbedMode();
1866           this.mTabsProgressListeners.push(aListener);
1867         </body>
1868       </method>
1870       <method name="removeTabsProgressListener">
1871         <parameter name="aListener"/>
1872         <body>
1873         <![CDATA[
1874           this.mTabsProgressListeners =
1875             this.mTabsProgressListeners.filter(function (l) l != aListener);
1876         ]]>
1877         </body>
1878       </method>
1880       <method name="getBrowserForTab">
1881         <parameter name="aTab"/>
1882         <body>
1883         <![CDATA[
1884           return aTab.linkedBrowser;
1885         ]]>
1886         </body>
1887       </method>
1889       <method name="showOnlyTheseTabs">
1890         <parameter name="aTabs"/>
1891         <body>
1892         <![CDATA[
1893           Array.forEach(this.tabs, function(tab) {
1894             if (aTabs.indexOf(tab) == -1)
1895               this.hideTab(tab);
1896             else
1897               this.showTab(tab);
1898           }, this);
1899         ]]>
1900         </body>
1901       </method>
1903       <method name="showTab">
1904         <parameter name="aTab"/>
1905         <body>
1906         <![CDATA[
1907           if (aTab.hidden) {
1908             aTab.removeAttribute("hidden");
1909             this.tabContainer.adjustTabstrip();
1910             let event = document.createEvent("Events");
1911             event.initEvent("TabShow", true, false);
1912             aTab.dispatchEvent(event);
1913           }
1914         ]]>
1915         </body>
1916       </method>
1918       <method name="hideTab">
1919         <parameter name="aTab"/>
1920         <body>
1921         <![CDATA[
1922           if (!aTab.hidden && !aTab.pinned && !aTab.selected &&
1923               this._removingTabs.indexOf(aTab) == -1) {
1924             aTab.setAttribute("hidden", "true");
1925             this.tabContainer.adjustTabstrip();
1926             let event = document.createEvent("Events");
1927             event.initEvent("TabHide", true, false);
1928             aTab.dispatchEvent(event);
1929           }
1930         ]]>
1931         </body>
1932       </method>
1934       <method name="selectTabAtIndex">
1935         <parameter name="aIndex"/>
1936         <parameter name="aEvent"/>
1937         <body>
1938         <![CDATA[
1939           let tabs = this.visibleTabs;
1941           // count backwards for aIndex < 0
1942           if (aIndex < 0)
1943             aIndex += tabs.length;
1945           if (aIndex >= 0 && aIndex < tabs.length)
1946             this.selectedTab = tabs[aIndex];
1948           if (aEvent) {
1949             aEvent.preventDefault();
1950             aEvent.stopPropagation();
1951           }
1952         ]]>
1953         </body>
1954       </method>
1956       <property name="selectedTab">
1957         <getter>
1958           return this.mTabBox.selectedTab;
1959         </getter>
1960         <setter>
1961           <![CDATA[
1962           // Update the tab
1963           this.mTabBox.selectedTab = val;
1964           return val;
1965           ]]>
1966         </setter>
1967       </property>
1969       <property name="selectedBrowser"
1970                 onget="return this.mCurrentBrowser;"
1971                 readonly="true"/>
1973       <property name="browsers" readonly="true">
1974        <getter>
1975           <![CDATA[
1976             return this._browsers ||
1977                    (this._browsers = Array.map(this.tabs, function (tab) tab.linkedBrowser));
1978           ]]>
1979         </getter>
1980       </property>
1982       <!-- Moves a tab to a new browser window, unless it's already the only tab
1983            in the current window, in which case this will do nothing. -->
1984       <method name="replaceTabWithWindow">
1985         <parameter name="aTab"/>
1986         <body>
1987           <![CDATA[
1988             if (this.tabs.length == 1)
1989               return null;
1991             // tell a new window to take the "dropped" tab
1992             return window.openDialog(getBrowserURL(), "_blank", "dialog=no,all", aTab);
1993           ]]>
1994         </body>
1995       </method>
1997       <method name="moveTabTo">
1998         <parameter name="aTab"/>
1999         <parameter name="aIndex"/>
2000         <body>
2001         <![CDATA[
2002           var oldPosition = aTab._tPos;
2003           if (oldPosition == aIndex)
2004             return;
2006           // Don't allow mixing pinned and unpinned tabs.
2007           if (aTab.pinned)
2008             aIndex = Math.min(aIndex, this._numPinnedTabs - 1);
2009           else
2010             aIndex = Math.max(aIndex, this._numPinnedTabs);
2011           if (oldPosition == aIndex)
2012             return;
2014           this._lastRelatedTab = null;
2016           this.mTabFilters.splice(aIndex, 0, this.mTabFilters.splice(aTab._tPos, 1)[0]);
2017           this.mTabListeners.splice(aIndex, 0, this.mTabListeners.splice(aTab._tPos, 1)[0]);
2019           aIndex = aIndex < aTab._tPos ? aIndex: aIndex+1;
2020           this.mCurrentTab._selected = false;
2021           // use .item() instead of [] because dragging to the end of the strip goes out of
2022           // bounds: .item() returns null (so it acts like appendChild), but [] throws
2023           this.tabContainer.insertBefore(aTab, this.tabs.item(aIndex));
2024           // invalidate cache, because tabContainer is about to change
2025           this._browsers = null;
2027           for (let i = 0; i < this.tabs.length; i++) {
2028             this.tabs[i]._tPos = i;
2029             this.tabs[i]._selected = false;
2030           }
2031           this.mCurrentTab._selected = true;
2032           this.tabContainer.mTabstrip.ensureElementIsVisible(this.mCurrentTab, false);
2034           if (aTab.pinned)
2035             this.tabContainer._positionPinnedTabs();
2037           var evt = document.createEvent("UIEvents");
2038           evt.initUIEvent("TabMove", true, false, window, oldPosition);
2039           aTab.dispatchEvent(evt);
2040         ]]>
2041         </body>
2042       </method>
2044       <method name="moveTabForward">
2045         <body>
2046           <![CDATA[
2047             var tabPos = this.mCurrentTab._tPos;
2048             if (tabPos < this.browsers.length - 1) {
2049               this.moveTabTo(this.mCurrentTab, tabPos + 1);
2050               this.mCurrentTab.focus();
2051             }
2052             else if (this.arrowKeysShouldWrap)
2053               this.moveTabToStart();
2054           ]]>
2055         </body>
2056       </method>
2058       <method name="moveTabBackward">
2059         <body>
2060           <![CDATA[
2061             var tabPos = this.mCurrentTab._tPos;
2062             if (tabPos > 0) {
2063               this.moveTabTo(this.mCurrentTab, tabPos - 1);
2064               this.mCurrentTab.focus();
2065             }
2066             else if (this.arrowKeysShouldWrap)
2067               this.moveTabToEnd();
2068           ]]>
2069         </body>
2070       </method>
2072       <method name="moveTabToStart">
2073         <body>
2074           <![CDATA[
2075             var tabPos = this.mCurrentTab._tPos;
2076             if (tabPos > 0) {
2077               this.moveTabTo(this.mCurrentTab, 0);
2078               this.mCurrentTab.focus();
2079             }
2080           ]]>
2081         </body>
2082       </method>
2084       <method name="moveTabToEnd">
2085         <body>
2086           <![CDATA[
2087             var tabPos = this.mCurrentTab._tPos;
2088             if (tabPos < this.browsers.length - 1) {
2089               this.moveTabTo(this.mCurrentTab,
2090                              this.browsers.length - 1);
2091               this.mCurrentTab.focus();
2092             }
2093           ]]>
2094         </body>
2095       </method>
2097       <method name="moveTabOver">
2098         <parameter name="aEvent"/>
2099         <body>
2100           <![CDATA[
2101             var direction = window.getComputedStyle(this.parentNode, null).direction;
2102             if ((direction == "ltr" && aEvent.keyCode == KeyEvent.DOM_VK_RIGHT) ||
2103                 (direction == "rtl" && aEvent.keyCode == KeyEvent.DOM_VK_LEFT))
2104               this.moveTabForward();
2105             else
2106               this.moveTabBackward();
2107           ]]>
2108         </body>
2109       </method>
2111       <method name="duplicateTab">
2112         <parameter name="aTab"/><!-- can be from a different window as well -->
2113         <body>
2114           <![CDATA[
2115             return Cc["@mozilla.org/browser/sessionstore;1"]
2116                      .getService(Ci.nsISessionStore)
2117                      .duplicateTab(window, aTab);
2118           ]]>
2119         </body>
2120       </method>
2122       <!-- BEGIN FORWARDED BROWSER PROPERTIES.  IF YOU ADD A PROPERTY TO THE BROWSER ELEMENT
2123            MAKE SURE TO ADD IT HERE AS WELL. -->
2124       <property name="canGoBack"
2125                 onget="return this.mCurrentBrowser.canGoBack;"
2126                 readonly="true"/>
2128       <property name="canGoForward"
2129                 onget="return this.mCurrentBrowser.canGoForward;"
2130                 readonly="true"/>
2132       <method name="goBack">
2133         <body>
2134           <![CDATA[
2135             return this.mCurrentBrowser.goBack();
2136           ]]>
2137         </body>
2138       </method>
2140       <method name="goForward">
2141         <body>
2142           <![CDATA[
2143             return this.mCurrentBrowser.goForward();
2144           ]]>
2145         </body>
2146       </method>
2148       <method name="reload">
2149         <body>
2150           <![CDATA[
2151             return this.mCurrentBrowser.reload();
2152           ]]>
2153         </body>
2154       </method>
2156       <method name="reloadWithFlags">
2157         <parameter name="aFlags"/>
2158         <body>
2159           <![CDATA[
2160             return this.mCurrentBrowser.reloadWithFlags(aFlags);
2161           ]]>
2162         </body>
2163       </method>
2165       <method name="stop">
2166         <body>
2167           <![CDATA[
2168             return this.mCurrentBrowser.stop();
2169           ]]>
2170         </body>
2171       </method>
2173       <!-- throws exception for unknown schemes -->
2174       <method name="loadURI">
2175         <parameter name="aURI"/>
2176         <parameter name="aReferrerURI"/>
2177         <parameter name="aCharset"/>
2178         <body>
2179           <![CDATA[
2180             return this.mCurrentBrowser.loadURI(aURI, aReferrerURI, aCharset);
2181           ]]>
2182         </body>
2183       </method>
2185       <!-- throws exception for unknown schemes -->
2186       <method name="loadURIWithFlags">
2187         <parameter name="aURI"/>
2188         <parameter name="aFlags"/>
2189         <parameter name="aReferrerURI"/>
2190         <parameter name="aCharset"/>
2191         <parameter name="aPostData"/>
2192         <body>
2193           <![CDATA[
2194             return this.mCurrentBrowser.loadURIWithFlags(aURI, aFlags, aReferrerURI, aCharset, aPostData);
2195           ]]>
2196         </body>
2197       </method>
2199       <method name="goHome">
2200         <body>
2201           <![CDATA[
2202             return this.mCurrentBrowser.goHome();
2203           ]]>
2204         </body>
2205       </method>
2207       <property name="homePage">
2208         <getter>
2209           <![CDATA[
2210             return this.mCurrentBrowser.homePage;
2211           ]]>
2212         </getter>
2213         <setter>
2214           <![CDATA[
2215             this.mCurrentBrowser.homePage = val;
2216             return val;
2217           ]]>
2218         </setter>
2219       </property>
2221       <method name="gotoIndex">
2222         <parameter name="aIndex"/>
2223         <body>
2224           <![CDATA[
2225             return this.mCurrentBrowser.gotoIndex(aIndex);
2226           ]]>
2227         </body>
2228       </method>
2230       <method name="attachFormFill">
2231         <body><![CDATA[
2232           for (var i = 0; i < this.mPanelContainer.childNodes.length; ++i) {
2233             var cb = this.getBrowserAtIndex(i);
2234             cb.attachFormFill();
2235           }
2236         ]]></body>
2237       </method>
2239       <method name="detachFormFill">
2240         <body><![CDATA[
2241           for (var i = 0; i < this.mPanelContainer.childNodes.length; ++i) {
2242             var cb = this.getBrowserAtIndex(i);
2243             cb.detachFormFill();
2244           }
2245         ]]></body>
2246       </method>
2248       <property name="pageReport"
2249                 onget="return this.mCurrentBrowser.pageReport;"
2250                 readonly="true"/>
2252       <property name="currentURI"
2253                 onget="return this.mCurrentBrowser.currentURI;"
2254                 readonly="true"/>
2256       <field name="_fastFind">null</field>
2257       <property name="fastFind"
2258                 readonly="true">
2259         <getter>
2260         <![CDATA[
2261           if (!this._fastFind) {
2262             this._fastFind = Components.classes["@mozilla.org/typeaheadfind;1"]
2263                                        .createInstance(Components.interfaces.nsITypeAheadFind);
2264             this._fastFind.init(this.docShell);
2265           }
2266           return this._fastFind;
2267         ]]>
2268         </getter>
2269       </property>
2271       <property name="docShell"
2272                 onget="return this.mCurrentBrowser.docShell"
2273                 readonly="true"/>
2275       <property name="webNavigation"
2276                 onget="return this.mCurrentBrowser.webNavigation"
2277                 readonly="true"/>
2279       <property name="webBrowserFind"
2280                 readonly="true"
2281                 onget="return this.mCurrentBrowser.webBrowserFind"/>
2283       <property name="webProgress"
2284                 readonly="true"
2285                 onget="return this.mCurrentBrowser.webProgress"/>
2287       <property name="contentWindow"
2288                 readonly="true"
2289                 onget="return this.mCurrentBrowser.contentWindow"/>
2291       <property name="sessionHistory"
2292                 onget="return this.mCurrentBrowser.sessionHistory;"
2293                 readonly="true"/>
2295       <property name="markupDocumentViewer"
2296                 onget="return this.mCurrentBrowser.markupDocumentViewer;"
2297                 readonly="true"/>
2299       <property name="contentViewerEdit"
2300                 onget="return this.mCurrentBrowser.contentViewerEdit;"
2301                 readonly="true"/>
2303       <property name="contentViewerFile"
2304                 onget="return this.mCurrentBrowser.contentViewerFile;"
2305                 readonly="true"/>
2307       <property name="documentCharsetInfo"
2308                 onget="return this.mCurrentBrowser.documentCharsetInfo;"
2309                 readonly="true"/>
2311       <property name="contentDocument"
2312                 onget="return this.mCurrentBrowser.contentDocument;"
2313                 readonly="true"/>
2315       <property name="contentTitle"
2316                 onget="return this.mCurrentBrowser.contentTitle;"
2317                 readonly="true"/>
2319       <property name="contentPrincipal"
2320                 onget="return this.mCurrentBrowser.contentPrincipal;"
2321                 readonly="true"/>
2323       <property name="securityUI"
2324                 onget="return this.mCurrentBrowser.securityUI;"
2325                 readonly="true"/>
2327       <method name="_handleKeyEvent">
2328         <parameter name="aEvent"/>
2329         <body><![CDATA[
2330           if (!aEvent.isTrusted) {
2331             // Don't let untrusted events mess with tabs.
2332             return;
2333           }
2335           if (aEvent.altKey)
2336             return;
2338           // We need to take care of FAYT-watching as long as the findbar
2339           // isn't initialized.  The checks on aEvent are copied from
2340           // _shouldFastFind (see findbar.xml).
2341           if (!gFindBarInitialized &&
2342               !(aEvent.ctrlKey || aEvent.metaKey) &&
2343               !aEvent.getPreventDefault()) {
2344             let charCode = aEvent.charCode;
2345             if (charCode) {
2346               let char = String.fromCharCode(charCode);
2347               if (char == "'" || char == "/" ||
2348                   Services.prefs.getBoolPref("accessibility.typeaheadfind")) {
2349                 gFindBar._onBrowserKeypress(aEvent);
2350                 return;
2351               }
2352             }
2353           }
2355 #ifdef XP_MACOSX
2356           if (!aEvent.metaKey)
2357             return;
2359           var offset = 1;
2360           switch (aEvent.charCode) {
2361             case '}'.charCodeAt(0):
2362               offset = -1;
2363             case '{'.charCodeAt(0):
2364               if (window.getComputedStyle(this, null).direction == "ltr")
2365                 offset *= -1;
2366               this.tabContainer.advanceSelectedTab(offset, true);
2367               aEvent.stopPropagation();
2368               aEvent.preventDefault();
2369           }
2370 #else
2371           if (aEvent.ctrlKey && !aEvent.shiftKey && !aEvent.metaKey &&
2372               aEvent.keyCode == KeyEvent.DOM_VK_F4 &&
2373               this.mTabBox.handleCtrlPageUpDown) {
2374             this.removeCurrentTab({animate: true});
2375             aEvent.stopPropagation();
2376             aEvent.preventDefault();
2377           }
2378 #endif
2379         ]]></body>
2380       </method>
2382       <property name="userTypedClear"
2383                 onget="return this.mCurrentBrowser.userTypedClear;"
2384                 onset="return this.mCurrentBrowser.userTypedClear = val;"/>
2386       <property name="userTypedValue"
2387                 onget="return this.mCurrentBrowser.userTypedValue;"
2388                 onset="return this.mCurrentBrowser.userTypedValue = val;"/>
2390       <method name="createTooltip">
2391         <parameter name="event"/>
2392         <body><![CDATA[
2393           event.stopPropagation();
2394           var tab = document.tooltipNode;
2395           if (tab.localName != "tab") {
2396             event.preventDefault();
2397             return;
2398           }
2399           event.target.setAttribute("label", tab.mOverCloseButton ?
2400                                              tab.getAttribute("closetabtext") :
2401                                              tab.getAttribute("label"));
2402         ]]></body>
2403       </method>
2405       <method name="handleEvent">
2406         <parameter name="aEvent"/>
2407         <body><![CDATA[
2408           switch (aEvent.type) {
2409             case "keypress":
2410               this._handleKeyEvent(aEvent);
2411               break;
2412           }
2413         ]]></body>
2414       </method>
2416       <constructor>
2417         <![CDATA[
2418           this.mCurrentBrowser = this.mPanelContainer.childNodes[0].firstChild.firstChild;
2419           this.mCurrentTab = this.tabContainer.firstChild;
2420           document.addEventListener("keypress", this, false);
2422           var uniqueId = "panel" + Date.now();
2423           this.mPanelContainer.childNodes[0].id = uniqueId;
2424           this.mCurrentTab.linkedPanel = uniqueId;
2425           this.mCurrentTab._tPos = 0;
2426           this.mCurrentTab.linkedBrowser = this.mCurrentBrowser;
2428           // set up the shared autoscroll popup
2429           this._autoScrollPopup = this.mCurrentBrowser._createAutoScrollPopup();
2430           this._autoScrollPopup.id = "autoscroller";
2431           this.appendChild(this._autoScrollPopup);
2432           this.mCurrentBrowser.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
2433           this.mCurrentBrowser.droppedLinkHandler = handleDroppedLink;
2434         ]]>
2435       </constructor>
2437       <destructor>
2438         <![CDATA[
2439           for (var i = 0; i < this.mTabListeners.length; ++i) {
2440             let browser = this.getBrowserAtIndex(i);
2441             if (browser.registeredOpenURI) {
2442               this._placesAutocomplete.unregisterOpenPage(browser.registeredOpenURI);
2443               delete browser.registeredOpenURI;
2444             }
2445             browser.webProgress.removeProgressListener(this.mTabFilters[i]);
2446             this.mTabFilters[i].removeProgressListener(this.mTabListeners[i]);
2447             this.mTabFilters[i] = null;
2448             this.mTabListeners[i].destroy();
2449             this.mTabListeners[i] = null;
2450           }
2451           document.removeEventListener("keypress", this, false);
2452         ]]>
2453       </destructor>
2455       <!-- Deprecated stuff, implemented for backwards compatibility. -->
2456       <method name="setStripVisibilityTo">
2457         <parameter name="aShow"/>
2458         <body>
2459           this.tabContainer.visible = aShow;
2460         </body>
2461       </method>
2462       <method name="getStripVisibility">
2463         <body>
2464           return this.tabContainer.visible;
2465         </body>
2466       </method>
2467       <property name="mContextTab" readonly="true"
2468                 onget="return TabContextMenu.contextTab;"/>
2469       <property name="mPrefs" readonly="true"
2470                 onget="return Services.prefs;"/>
2471       <property name="mTabContainer" readonly="true"
2472                 onget="return this.tabContainer;"/>
2473       <property name="mTabs" readonly="true"
2474                 onget="return this.tabs;"/>
2475       <!--
2476         - Compatibility hack: several extensions depend on this property to
2477         - access the tab context menu or tab container, so keep that working for
2478         - now. Ideally we can remove this once extensions are using
2479         - tabbrowser.tabContextMenu and tabbrowser.tabContainer directly.
2480         -->
2481       <property name="mStrip" readonly="true">
2482         <getter>
2483         <![CDATA[
2484           return ({
2485             self: this,
2486             childNodes: [null, this.tabContextMenu, this.tabContainer],
2487             firstChild: { nextSibling: this.tabContextMenu },
2488             getElementsByAttribute: function (attr, attrValue) {
2489               if (attr == "anonid" && attrValue == "tabContextMenu")
2490                 return [this.self.tabContextMenu];
2491               return [];
2492             },
2493             // Also support adding event listeners (forward to the tab container)
2494             addEventListener: function (a,b,c) { this.self.tabContainer.addEventListener(a,b,c); },
2495             removeEventListener: function (a,b,c) { this.self.tabContainer.removeEventListener(a,b,c); }
2496           });
2497         ]]>
2498         </getter>
2499       </property>
2500     </implementation>
2502     <handlers>
2503       <handler event="DOMWindowClose" phase="capturing">
2504         <![CDATA[
2505           if (!event.isTrusted)
2506             return;
2508           if (this.tabs.length == 1)
2509             return;
2511           var tab = this._getTabForContentWindow(event.target);
2512           if (tab) {
2513             this.removeTab(tab);
2514             event.preventDefault();
2515           }
2516         ]]>
2517       </handler>
2518       <handler event="DOMWillOpenModalDialog" phase="capturing">
2519         <![CDATA[
2520           if (!event.isTrusted)
2521             return;
2523           // We're about to open a modal dialog, make sure the opening
2524           // tab is brought to the front.
2525           this.selectedTab = this._getTabForContentWindow(event.target.top);
2526         ]]>
2527       </handler>
2528       <handler event="DOMTitleChanged">
2529         <![CDATA[
2530           if (!event.isTrusted)
2531             return;
2533           var contentWin = event.target.defaultView;
2534           if (contentWin != contentWin.top)
2535             return;
2537           var tab = this._getTabForContentWindow(contentWin);
2538           var titleChanged = this.setTabTitle(tab);
2539           if (titleChanged && !tab.selected && !tab.hasAttribute("busy"))
2540             tab.setAttribute("titlechanged", "true");
2541         ]]>
2542       </handler>
2543     </handlers>
2544   </binding>
2546   <binding id="tabbrowser-tabbox"
2547            extends="chrome://global/content/bindings/tabbox.xml#tabbox">
2548     <implementation>
2549       <property name="tabs" readonly="true"
2550                 onget="return document.getBindingParent(this).tabContainer;"/>
2551     </implementation>
2552   </binding>
2554   <binding id="tabbrowser-arrowscrollbox" extends="chrome://global/content/bindings/scrollbox.xml#arrowscrollbox-clicktoscroll">
2555     <implementation>
2556       <!-- Override scrollbox.xml method, since our scrollbox's children are
2557            inherited from the binding parent -->
2558       <method name="_getScrollableElements">
2559         <body><![CDATA[
2560           return Array.filter(document.getBindingParent(this).childNodes,
2561                               this._canScrollToElement, this);
2562         ]]></body>
2563       </method>
2564       <method name="_canScrollToElement">
2565         <parameter name="tab"/>
2566         <body><![CDATA[
2567           return !tab.pinned && !tab.hidden;
2568         ]]></body>
2569       </method>
2570     </implementation>
2572     <handlers>
2573       <handler event="underflow"><![CDATA[
2574          if (event.detail == 0)
2575            return; // Ignore vertical events
2577          var tabs = document.getBindingParent(this);
2578          tabs.removeAttribute("overflow");
2580          tabs.tabbrowser._removingTabs.forEach(tabs.tabbrowser.removeTab,
2581                                                tabs.tabbrowser);
2583          tabs._positionPinnedTabs();
2584       ]]></handler>
2585       <handler event="overflow"><![CDATA[
2586          if (event.detail == 0)
2587            return; // Ignore vertical events
2589          var tabs = document.getBindingParent(this);
2590          tabs.setAttribute("overflow", "true");
2591          tabs._positionPinnedTabs();
2592       ]]></handler>
2593     </handlers>
2594   </binding>
2596   <binding id="tabbrowser-tabs"
2597            extends="chrome://global/content/bindings/tabbox.xml#tabs">
2598     <resources>
2599       <stylesheet src="chrome://browser/content/tabbrowser.css"/>
2600     </resources>
2602     <content>
2603       <xul:hbox align="start">
2604         <xul:image class="tab-drop-indicator" anonid="tab-drop-indicator" collapsed="true"/>
2605       </xul:hbox>
2606       <xul:arrowscrollbox anonid="arrowscrollbox" orient="horizontal" flex="1"
2607                           style="min-width: 1px;"
2608 #ifndef XP_MACOSX
2609                           clicktoscroll="true"
2610 #endif
2611                           class="tabbrowser-arrowscrollbox">
2612 # This is a hack to circumvent bug 472020, otherwise the tabs show up on the
2613 # right of the newtab button.
2614         <children includes="tab"/>
2615 # This is to ensure anything extensions put here will go before the newtab
2616 # button, necessary due to the previous hack.
2617         <children/>
2618         <xul:toolbarbutton class="tabs-newtab-button"
2619                            command="cmd_newNavigatorTab"
2620                            onclick="checkForMiddleClick(this, event);"
2621                            tooltiptext="&newTabButton.tooltip;"/>
2622       </xul:arrowscrollbox>
2623     </content>
2625     <implementation implements="nsIDOMEventListener">
2626       <constructor>
2627         <![CDATA[
2628           this.mTabClipWidth = Services.prefs.getIntPref("browser.tabs.tabClipWidth");
2629           this.mCloseButtons = Services.prefs.getIntPref("browser.tabs.closeButtons");
2630           this._closeWindowWithLastTab = Services.prefs.getBoolPref("browser.tabs.closeWindowWithLastTab");
2632           var tab = this.firstChild;
2633           tab.setAttribute("label",
2634                            this.tabbrowser.mStringBundle.getString("tabs.emptyTabTitle"));
2635           tab.setAttribute("crop", "end");
2636           tab.setAttribute("validate", "never");
2637           tab.setAttribute("onerror", "this.removeAttribute('image');");
2638           this.adjustTabstrip();
2640           Services.prefs.addObserver("browser.tabs.", this._prefObserver, false);
2641           window.addEventListener("resize", this, false);
2643           if (window.TabsInTitlebar)
2644             TabsInTitlebar.allowedBy("tabs-visible", this.visible);
2645         ]]>
2646       </constructor>
2648       <destructor>
2649         <![CDATA[
2650           Services.prefs.removeObserver("browser.tabs.", this._prefObserver);
2651         ]]>
2652       </destructor>
2654       <field name="tabbrowser" readonly="true">
2655         document.getElementById(this.getAttribute("tabbrowser"));
2656       </field>
2658       <field name="tabbox" readonly="true">
2659         this.tabbrowser.mTabBox;
2660       </field>
2662       <field name="contextMenu" readonly="true">
2663         document.getElementById("tabContextMenu");
2664       </field>
2666       <field name="mTabstripWidth">0</field>
2668       <field name="mTabstrip">
2669         document.getAnonymousElementByAttribute(this, "anonid", "arrowscrollbox");
2670       </field>
2672       <field name="_prefObserver"><![CDATA[({
2673         tabContainer: this,
2675         observe: function (subject, topic, data) {
2676           switch (data) {
2677             case "browser.tabs.closeButtons":
2678               this.tabContainer.mCloseButtons = Services.prefs.getIntPref(data);
2679               this.tabContainer.adjustTabstrip();
2680               break;
2681             case "browser.tabs.autoHide":
2682               this.tabContainer.updateVisibility();
2683               break;
2684             case "browser.tabs.closeWindowWithLastTab":
2685               this.tabContainer._closeWindowWithLastTab = Services.prefs.getBoolPref(data);
2686               this.tabContainer.adjustTabstrip();
2687               break;
2688           }
2689         }
2690       });]]></field>
2691       <field name="_blockDblClick">false</field>
2693       <field name="_tabDropIndicator">
2694         document.getAnonymousElementByAttribute(this, "anonid", "tab-drop-indicator");
2695       </field>
2697       <field name="_dragOverDelay">350</field>
2698       <field name="_dragTime">0</field>
2700       <field name="_container" readonly="true"><![CDATA[
2701         this.parentNode && this.parentNode.localName == "toolbar" ? this.parentNode : this;
2702       ]]></field>
2704       <property name="visible"
2705                 onget="return !this._container.collapsed;">
2706         <setter><![CDATA[
2707           if (val == this.visible)
2708             return val;
2710           this._container.collapsed = !val;
2712           if (val)
2713             this.tabbrowser.enterTabbedMode();
2715           document.getElementById("menu_closeWindow").hidden = !val;
2716           document.getElementById("menu_close").setAttribute("label",
2717             this.tabbrowser.mStringBundle.getString(val ? "tabs.closeTab" : "tabs.close"));
2719           if (window.TabsInTitlebar)
2720             TabsInTitlebar.allowedBy("tabs-visible", val);
2722           return val;
2723         ]]></setter>
2724       </property>
2726       <method name="updateVisibility">
2727         <body><![CDATA[
2728           if (this.childNodes.length - this.tabbrowser._removingTabs.length == 1 &&
2729               window.toolbar.visible)
2730             this.visible = !Services.prefs.getBoolPref("browser.tabs.autoHide");
2731           else
2732             this.visible = true;
2733         ]]></body>
2734       </method>
2736       <method name="adjustTabstrip">
2737         <body><![CDATA[
2738           // modes for tabstrip
2739           // 0 - button on active tab only
2740           // 1 - close buttons on all tabs
2741           // 2 - no close buttons at all
2742           // 3 - close button at the end of the tabstrip
2743           switch (this.mCloseButtons) {
2744           case 0:
2745             if (this.childNodes.length == 1 && this._closeWindowWithLastTab)
2746               this.setAttribute("closebuttons", "hidden");
2747             else
2748               this.setAttribute("closebuttons", "activetab");
2749             break;
2750           case 1:
2751             if (this.childNodes.length == 1) {
2752               if (this._closeWindowWithLastTab)
2753                 this.setAttribute("closebuttons", "hidden");
2754               else
2755                 this.setAttribute("closebuttons", "alltabs");
2756             } else {
2757               let tab = this.tabbrowser.visibleTabs[this.tabbrowser._numPinnedTabs];
2758               if (tab && tab.getBoundingClientRect().width > this.mTabClipWidth)
2759                 this.setAttribute("closebuttons", "alltabs");
2760               else
2761                 this.setAttribute("closebuttons", "activetab");
2762             }
2763             break;
2764           case 2:
2765           case 3:
2766             this.setAttribute("closebuttons", "never");
2767             break;
2768           }
2769           var tabstripClosebutton = document.getElementById("tabs-closebutton");
2770           if (tabstripClosebutton && tabstripClosebutton.parentNode == this._container)
2771             tabstripClosebutton.collapsed = this.mCloseButtons != 3;
2772         ]]></body>
2773       </method>
2775       <method name="_handleTabSelect">
2776         <body><![CDATA[
2777           this.mTabstrip.ensureElementIsVisible(this.selectedItem);
2778         ]]></body>
2779       </method>
2781       <method name="_fillTrailingGap">
2782         <body><![CDATA[
2783           try {
2784             // if we're at the right side (and not the logical end,
2785             // which is why this works for both LTR and RTL)
2786             // of the tabstrip, we need to ensure that we stay
2787             // completely scrolled to the right side
2788             var tabStrip = this.mTabstrip;
2789             if (tabStrip.scrollPosition + tabStrip.scrollClientSize >
2790                 tabStrip.scrollSize)
2791               tabStrip.scrollByPixels(-1);
2792           } catch (e) {}
2793         ]]></body>
2794       </method>
2796       <method name="_positionPinnedTabs">
2797         <body><![CDATA[
2798           var numPinned = this.tabbrowser._numPinnedTabs;
2799           var pinnedOnly = (numPinned == this.tabbrowser.visibleTabs.length);
2801           if (pinnedOnly)
2802             this.setAttribute("pinnedonly", "true");
2803           else
2804             this.removeAttribute("pinnedonly");
2806           var scrollButtonWidth = (this.getAttribute("overflow") != "true" || pinnedOnly) ? 0 :
2807                                   this.mTabstrip._scrollButtonDown.scrollWidth;
2808           var paddingStart = this.mTabstrip.scrollboxPaddingStart;
2809           var width = 0;
2811           for (var i = numPinned - 1; i >= 0; i--) {
2812             let tab = this.childNodes[i];
2813             width += pinnedOnly ? 0 : tab.scrollWidth;
2814             if (this.getAttribute("overflow") != "true")
2815               tab.style.MozMarginStart = - (width + scrollButtonWidth) + "px";
2816             else
2817               tab.style.MozMarginStart = - (width + scrollButtonWidth + paddingStart) + "px";
2818           }
2819           if (width == 0 || this.getAttribute("overflow") != "true")
2820             this.style.MozMarginStart = width + "px";
2821           else
2822             this.style.MozMarginStart = width + paddingStart + "px";
2823           this.mTabstrip.ensureElementIsVisible(this.selectedItem, false);
2824         ]]></body>
2825       </method>
2827       <method name="handleEvent">
2828         <parameter name="aEvent"/>
2829         <body><![CDATA[
2830           switch (aEvent.type) {
2831             case "resize":
2832               if (aEvent.target != window)
2833                 break;
2834               var width = this.mTabstrip.boxObject.width;
2835               if (width != this.mTabstripWidth) {
2836                 this.adjustTabstrip();
2837                 this._fillTrailingGap();
2838                 this._handleTabSelect();
2839                 this.mTabstripWidth = width;
2840               }
2841               break;
2842           }
2843         ]]></body>
2844       </method>
2846       <field name="_animateElement">
2847         this.mTabstrip._scrollButtonDown;
2848       </field>
2850       <method name="_notifyBackgroundTab">
2851         <parameter name="aTab"/>
2852         <body><![CDATA[
2853           if (aTab.pinned)
2854             return;
2856           var scrollRect = this.mTabstrip.scrollClientRect;
2857           var tab = aTab.getBoundingClientRect();
2859           // Is the new tab already completely visible?
2860           if (scrollRect.left <= tab.left && tab.right <= scrollRect.right)
2861             return;
2863           if (this.mTabstrip.smoothScroll) {
2864             let selected = !this.selectedItem.pinned &&
2865                            this.selectedItem.getBoundingClientRect();
2867             // Can we make both the new tab and the selected tab completely visible?
2868             if (!selected ||
2869                 Math.max(tab.right - selected.left, selected.right - tab.left) <=
2870                   scrollRect.width) {
2871               this.mTabstrip.ensureElementIsVisible(aTab);
2872               return;
2873             }
2875             this.mTabstrip._smoothScrollByPixels(this.mTabstrip._isRTLScrollbox ?
2876                                                  selected.right - scrollRect.right :
2877                                                  selected.left - scrollRect.left);
2878           }
2880           if (!this._animateElement.hasAttribute("notifybgtab")) {
2881             this._animateElement.setAttribute("notifybgtab", "true");
2882             setTimeout(function (ele) {
2883               ele.removeAttribute("notifybgtab");
2884             }, 150, this._animateElement);
2885           }
2886         ]]></body>
2887       </method>
2889       <method name="_getDragTargetTab">
2890         <parameter name="event"/>
2891         <body><![CDATA[
2892           let tab = event.target.localName == "tab" ? event.target : null;
2893           if (tab &&
2894               (event.type == "drop" || event.type == "dragover") &&
2895               event.dataTransfer.dropEffect == "link") {
2896             let boxObject = tab.boxObject;
2897             if (event.screenX < boxObject.screenX + boxObject.width * .25 ||
2898                 event.screenX > boxObject.screenX + boxObject.width * .75)
2899               return null;
2900           }
2901           return tab;
2902         ]]></body>
2903       </method>
2905       <method name="_getDropIndex">
2906         <parameter name="event"/>
2907         <body><![CDATA[
2908           var tabs = this.childNodes;
2909           var tab = this._getDragTargetTab(event);
2910           if (window.getComputedStyle(this, null).direction == "ltr") {
2911             for (let i = tab ? tab._tPos : 0; i < tabs.length; i++)
2912               if (event.screenX < tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2) 
2913                 return i;
2914           } else {
2915             for (let i = tab ? tab._tPos : 0; i < tabs.length; i++)
2916               if (event.screenX > tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2)
2917                 return i;
2918           }
2919           return tabs.length;
2920         ]]></body>
2921       </method>
2923       <method name="_setEffectAllowedForDataTransfer">
2924         <parameter name="event"/>
2925         <body><![CDATA[
2926           var dt = event.dataTransfer;
2927           // Disallow dropping multiple items
2928           if (dt.mozItemCount > 1)
2929             return dt.effectAllowed = "none";
2931           var types = dt.mozTypesAt(0);
2932           var sourceNode = null;
2933           // tabs are always added as the first type
2934           if (types[0] == TAB_DROP_TYPE) {
2935             var sourceNode = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
2936             if (sourceNode instanceof XULElement &&
2937                 sourceNode.localName == "tab" &&
2938                 (sourceNode.parentNode == this ||
2939                  (sourceNode.ownerDocument.defaultView instanceof ChromeWindow &&
2940                   sourceNode.ownerDocument.documentElement.getAttribute("windowtype") == "navigator:browser"))) {
2941               if (sourceNode.parentNode == this &&
2942                   (event.screenX >= sourceNode.boxObject.screenX &&
2943                     event.screenX <= (sourceNode.boxObject.screenX +
2944                                        sourceNode.boxObject.width))) {
2945                 return dt.effectAllowed = "none";
2946               }
2948               return dt.effectAllowed = "copyMove";
2949             }
2950           }
2952           if (browserDragAndDrop.canDropLink(event)) {
2953             // Here we need to do this manually
2954             return dt.effectAllowed = dt.dropEffect = "link";
2955           }
2956           return dt.effectAllowed = "none";
2957         ]]></body>
2958       </method>
2960       <method name="_continueScroll">
2961         <parameter name="event"/>
2962         <body><![CDATA[
2963           // Workaround for bug 481904: Dragging a tab stops scrolling at
2964           // the tab's position when dragging to the first/last tab and back.
2965           var t = this.selectedItem;
2966           if (event.screenX >= t.boxObject.screenX &&
2967               event.screenX <= t.boxObject.screenX + t.boxObject.width &&
2968               event.screenY >= t.boxObject.screenY &&
2969               event.screenY <= t.boxObject.screenY + t.boxObject.height)
2970             this.mTabstrip.ensureElementIsVisible(t);
2971         ]]></body>
2972       </method>
2974       <method name="_handleNewTab">
2975         <parameter name="tab"/>
2976         <body><![CDATA[
2977           if (tab.parentNode != this)
2978             return;
2980           this.adjustTabstrip();
2982           if (tab.getAttribute("selected") == "true") {
2983             this._fillTrailingGap();
2984             this._handleTabSelect();
2985           } else {
2986             this._notifyBackgroundTab(tab);
2987           }
2989           // XXXmano: this is a temporary workaround for bug 345399
2990           // We need to manually update the scroll buttons disabled state
2991           // if a tab was inserted to the overflow area or removed from it
2992           // without any scrolling and when the tabbar has already
2993           // overflowed.
2994           this.mTabstrip._updateScrollButtonsDisabledState();
2995         ]]></body>
2996       </method>
2997       
2998       <method name="_canAdvanceToTab">
2999         <parameter name="aTab"/>
3000         <body>
3001         <![CDATA[
3002           return this.tabbrowser._removingTabs.indexOf(aTab) == -1;
3003         ]]>
3004         </body>
3005       </method>
3007       <!-- Deprecated stuff, implemented for backwards compatibility. -->
3008       <property name="mTabstripClosebutton" readonly="true"
3009                 onget="return document.getElementById('tabs-closebutton');"/>
3010       <property name="mAllTabsPopup" readonly="true"
3011                 onget="return document.getElementById('alltabs-popup');"/>
3012     </implementation>
3014     <handlers>
3015       <handler event="TabSelect" action="this._handleTabSelect();"/>
3017       <handler event="transitionend"><![CDATA[
3018         if (event.propertyName != "max-width")
3019           return;
3021         var tab = event.target;
3023         if (tab.getAttribute("fadein") == "true")
3024           this._handleNewTab(tab);
3025         else if (this.tabbrowser._removingTabs.indexOf(tab) > -1)
3026           this.tabbrowser._endRemoveTab(tab);
3027       ]]></handler>
3029       <handler event="dblclick"><![CDATA[
3030         // See hack note in the tabbrowser-close-button binding
3031         if (!this._blockDblClick && event.button == 0 &&
3032             event.originalTarget.localName == "box")
3033           BrowserOpenTab();
3034       ]]></handler>
3036       <handler event="click"><![CDATA[
3037         if (event.button != 1)
3038           return;
3040         if (event.target.localName == "tab") {
3041           if (this.childNodes.length > 1 || !this._closeWindowWithLastTab)
3042             this.tabbrowser.removeTab(event.target, {animate: true});
3043         } else if (event.originalTarget.localName == "box") {
3044           BrowserOpenTab();
3045         } else {
3046           return;
3047         }
3049         event.stopPropagation();
3050       ]]></handler>
3052       <handler event="keypress"><![CDATA[
3053         if (event.altKey || event.shiftKey ||
3054 #ifdef XP_MACOSX
3055             !event.metaKey)
3056 #else
3057             !event.ctrlKey || event.metaKey)
3058 #endif
3059           return;
3061         switch (event.keyCode) {
3062           case KeyEvent.DOM_VK_UP:
3063             this.tabbrowser.moveTabBackward();
3064             break;
3065           case KeyEvent.DOM_VK_DOWN:
3066             this.tabbrowser.moveTabForward();
3067             break;
3068           case KeyEvent.DOM_VK_RIGHT:
3069           case KeyEvent.DOM_VK_LEFT:
3070             this.tabbrowser.moveTabOver(event);
3071             break;
3072           case KeyEvent.DOM_VK_HOME:
3073             this.tabbrowser.moveTabToStart();
3074             break;
3075           case KeyEvent.DOM_VK_END:
3076             this.tabbrowser.moveTabToEnd();
3077             break;
3078           default:
3079             // Stop the keypress event for the above keyboard
3080             // shortcuts only.
3081             return;
3082         }
3083         event.stopPropagation();
3084         event.preventDefault();
3085       ]]></handler>
3087       <handler event="dragstart"><![CDATA[
3088         var tab = this._getDragTargetTab(event);
3089         if (!tab)
3090           return;
3092         let dt = event.dataTransfer;
3093         dt.mozSetDataAt(TAB_DROP_TYPE, tab, 0);
3094         let uri = this.tabbrowser.getBrowserForTab(tab).currentURI;
3095         let spec = uri ? uri.spec : "about:blank";
3097         // We must not set text/x-moz-url or text/plain data here,
3098         // otherwise trying to deatch the tab by dropping it on the desktop
3099         // may result in an "internet shortcut"
3100         dt.mozSetDataAt("text/x-moz-text-internal", spec, 0);
3102         // Set the cursor to an arrow during tab drags.
3103         dt.mozCursor = "default";
3105         let canvas = tabPreviews.capture(tab, false);
3106         dt.setDragImage(canvas, 0, 0);
3107         event.stopPropagation();
3108       ]]></handler>
3110       <handler event="dragover"><![CDATA[
3111         var effects = this._setEffectAllowedForDataTransfer(event);
3113         var ind = this._tabDropIndicator;
3114         if (effects == "" || effects == "none") {
3115           ind.collapsed = true;
3116           this._continueScroll(event);
3117           return;
3118         }
3119         event.preventDefault();
3120         event.stopPropagation();
3122         var tabStrip = this.mTabstrip;
3123         var ltr = (window.getComputedStyle(this, null).direction == "ltr");
3125         // autoscroll the tab strip if we drag over the scroll
3126         // buttons, even if we aren't dragging a tab, but then
3127         // return to avoid drawing the drop indicator
3128         var pixelsToScroll = 0;
3129         if (this.getAttribute("overflow") == "true") {
3130           var targetAnonid = event.originalTarget.getAttribute("anonid");
3131           switch (targetAnonid) {
3132             case "scrollbutton-up":
3133               pixelsToScroll = tabStrip.scrollIncrement * -1;
3134               break;
3135             case "scrollbutton-down":
3136               pixelsToScroll = tabStrip.scrollIncrement;
3137               break;
3138           }
3139           if (pixelsToScroll)
3140             tabStrip.scrollByPixels((ltr ? 1 : -1) * pixelsToScroll);
3141         }
3143         if (effects == "link") {
3144           let tab = this._getDragTargetTab(event);
3145           if (tab) {
3146             if (!this._dragTime)
3147               this._dragTime = Date.now();
3148             if (Date.now() >= this._dragTime + this._dragOverDelay)
3149               this.selectedItem = tab;
3150             ind.collapsed = true;
3151             return;
3152           }
3153         }
3155         var newIndex = this._getDropIndex(event);
3156         var scrollRect = tabStrip.scrollClientRect;
3157         var rect = this.getBoundingClientRect();
3158         var minMargin = scrollRect.left - rect.left;
3159         var maxMargin = Math.min(minMargin + scrollRect.width, 
3160                                  scrollRect.right);
3161         if (!ltr)
3162           [minMargin, maxMargin] = [this.clientWidth - maxMargin,
3163                                     this.clientWidth - minMargin];
3164         var newMargin;
3165         if (pixelsToScroll) {
3166           // if we are scrolling, put the drop indicator at the edge
3167           // so that it doesn't jump while scrolling
3168           newMargin = (pixelsToScroll > 0) ? maxMargin : minMargin;
3169         }
3170         else {
3171           if (newIndex == this.childNodes.length) {
3172             let tabRect = this.childNodes[newIndex-1].getBoundingClientRect();
3173             if (ltr)
3174               newMargin = tabRect.right - rect.left;
3175             else
3176               newMargin = rect.right - tabRect.left;
3177           }
3178           else {
3179             let tabRect = this.childNodes[newIndex].getBoundingClientRect();
3180             if (ltr)
3181               newMargin = tabRect.left - rect.left;
3182             else
3183               newMargin = rect.right - tabRect.right;
3184           }
3185         }
3187         ind.collapsed = false;
3189         newMargin += ind.clientWidth / 2;
3190         if (!ltr)
3191           newMargin *= -1;
3193         ind.style.MozTransform = "translate(" + Math.round(newMargin) + "px)";
3194         ind.style.MozMarginStart = (-ind.clientWidth) + "px";
3195         ind.style.marginTop = (-ind.clientHeight) + "px";
3196       ]]></handler>
3198       <handler event="drop"><![CDATA[
3199         var dt = event.dataTransfer;
3200         var dropEffect = dt.dropEffect;
3201         var draggedTab;
3202         if (dropEffect != "link") { // copy or move
3203           draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
3204           // not our drop then
3205           if (!draggedTab)
3206             return;
3207         }
3209         this._tabDropIndicator.collapsed = true;
3210         event.stopPropagation();
3212         if (draggedTab && (dropEffect == "copy" ||
3213             draggedTab.parentNode == this)) {
3214           let newIndex = this._getDropIndex(event);
3215           if (dropEffect == "copy") {
3216             // copy the dropped tab (wherever it's from)
3217             let newTab = this.tabbrowser.duplicateTab(draggedTab);
3218             this.tabbrowser.moveTabTo(newTab, newIndex);
3219             if (draggedTab.parentNode != this || event.shiftKey)
3220               this.selectedItem = newTab;
3221           } else {
3222             // move the dropped tab
3223             if (newIndex > draggedTab._tPos)
3224               newIndex--;
3226             if (draggedTab.pinned) {
3227               if (newIndex >= this.tabbrowser._numPinnedTabs)
3228                 this.tabbrowser.unpinTab(draggedTab);
3229             } else {
3230               if (newIndex <= this.tabbrowser._numPinnedTabs - 1)
3231                 this.tabbrowser.pinTab(draggedTab);
3232             }
3234             this.tabbrowser.moveTabTo(draggedTab, newIndex);
3235           }
3236         } else if (draggedTab) {
3237           // swap the dropped tab with a new one we create and then close
3238           // it in the other window (making it seem to have moved between
3239           // windows)
3240           let newIndex = this._getDropIndex(event);
3241           let newTab = this.tabbrowser.addTab("about:blank");
3242           let newBrowser = this.tabbrowser.getBrowserForTab(newTab);
3243           // Stop the about:blank load
3244           newBrowser.stop();
3245           // make sure it has a docshell
3246           newBrowser.docShell;
3248           this.tabbrowser.moveTabTo(newTab, newIndex);
3250           this.tabbrowser.swapBrowsersAndCloseOther(newTab, draggedTab);
3252           // We need to select the tab after we've done
3253           // swapBrowsersAndCloseOther, so that the updateCurrentBrowser
3254           // it triggers will correctly update our URL bar.
3255           this.tabbrowser.selectedTab = newTab;
3256         } else {
3257           let url = browserDragAndDrop.drop(event, { });
3259           // valid urls don't contain spaces ' '; if we have a space it isn't a valid url.
3260           // Also disallow dropping javascript: or data: urls--bail out
3261           if (!url || !url.length || url.indexOf(" ", 0) != -1 ||
3262               /^\s*(javascript|data):/.test(url))
3263             return;
3265           let bgLoad = Services.prefs.getBoolPref("browser.tabs.loadInBackground");
3267           if (event.shiftKey)
3268             bgLoad = !bgLoad;
3270           let tab = this._getDragTargetTab(event);
3271           if (!tab || dropEffect == "copy") {
3272             // We're adding a new tab.
3273             let newIndex = this._getDropIndex(event);
3274             let newTab = this.tabbrowser.loadOneTab(getShortcutOrURI(url), {inBackground: bgLoad});
3275             this.tabbrowser.moveTabTo(newTab, newIndex);
3276           } else {
3277             // Load in an existing tab.
3278             try {
3279               this.tabbrowser.getBrowserForTab(tab).loadURI(getShortcutOrURI(url));
3280               if (!bgLoad)
3281                 this.selectedItem = tab;
3282             } catch(ex) {
3283               // Just ignore invalid urls
3284             }
3285           }
3286         }
3287       ]]></handler>
3289       <handler event="dragend"><![CDATA[
3290         // Note: while this case is correctly handled here, this event
3291         // isn't dispatched when the tab is moved within the tabstrip,
3292         // see bug 460801.
3294         // * mozUserCancelled = the user pressed ESC to cancel the drag
3295         var dt = event.dataTransfer;
3296         if (dt.mozUserCancelled || dt.dropEffect != "none")
3297           return;
3299         // Disable detach within the browser toolbox
3300         var eX = event.screenX;
3301         var wX = window.screenX;
3302         // check if the drop point is horizontally within the window
3303         if (eX > wX && eX < (wX + window.outerWidth)) {
3304           let bo = this.mTabstrip.boxObject;
3305           // also avoid detaching if the the tab was dropped too close to
3306           // the tabbar (half a tab)
3307           let endScreenY = bo.screenY + 1.5 * bo.height;
3308           let eY = event.screenY;
3309           if (eY < endScreenY && eY > window.screenY)
3310             return;
3311         }
3313         var draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
3314         this.tabbrowser.replaceTabWithWindow(draggedTab);
3315         event.stopPropagation();
3316       ]]></handler>
3318       <handler event="dragexit"><![CDATA[
3319         this._dragTime = 0;
3321         // This does not work at all (see bug 458613)
3322         var target = event.relatedTarget;
3323         while (target && target != this)
3324           target = target.parentNode;
3325         if (target)
3326           return;
3328         this._tabDropIndicator.collapsed = true;
3329         this._continueScroll(event);
3330         event.stopPropagation();
3331       ]]></handler>
3332     </handlers>
3333   </binding>
3335   <!-- close-tab-button binding
3336        This binding relies on the structure of the tabbrowser binding.
3337        Therefore it should only be used as a child of the tab or the tabs
3338        element (in both cases, when they are anonymous nodes of <tabbrowser>).
3339   -->
3340   <binding id="tabbrowser-close-tab-button"
3341            extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-image">
3342     <handlers>
3343       <handler event="click" button="0"><![CDATA[
3344         var bindingParent = document.getBindingParent(this);
3345         var tabContainer = bindingParent.parentNode;
3346         /* The only sequence in which a second click event (i.e. dblclik)
3347          * can be dispatched on an in-tab close button is when it is shown
3348          * after the first click (i.e. the first click event was dispatched
3349          * on the tab). This happens when we show the close button only on
3350          * the active tab. (bug 352021)
3351          * The only sequence in which a third click event can be dispatched
3352          * on an in-tab close button is when the tab was opened with a
3353          * double click on the tabbar. (bug 378344)
3354          * In both cases, it is most likely that the close button area has
3355          * been accidentally clicked, therefore we do not close the tab.
3356          *
3357          * We don't want to ignore processing of more than one click event,
3358          * though, since the user might actually be repeatedly clicking to
3359          * close many tabs at once.
3360          */
3361         if (event.detail > 1 && !this._ignoredClick) {
3362           this._ignoredClick = true;
3363           return;
3364         }
3366         // Reset the "ignored click" flag
3367         this._ignoredClick = false;
3369         tabContainer.tabbrowser.removeTab(bindingParent, {animate: true});
3370         tabContainer._blockDblClick = true;
3372         /* XXXmano hack (see bug 343628):
3373          * Since we're removing the event target, if the user
3374          * double-clicks this button, the dblclick event will be dispatched
3375          * with the tabbar as its event target (and explicit/originalTarget),
3376          * which treats that as a mouse gesture for opening a new tab.
3377          * In this context, we're manually blocking the dblclick event
3378          * (see dblclick handler).
3379          */
3380         var clickedOnce = false;
3381         function enableDblClick(event) {
3382           if (event.detail == 1 && !clickedOnce) {
3383             clickedOnce = true;
3384             return;
3385           }
3386           setTimeout(function() {
3387             tabContainer._blockDblClick = false;
3388           }, 0);
3389           tabContainer.removeEventListener("click", enableDblClick, false);
3390         }
3391         tabContainer.addEventListener("click", enableDblClick, false);
3392       ]]></handler>
3394       <handler event="dblclick" button="0" phase="capturing">
3395         // for the one-close-button case
3396         event.stopPropagation();
3397       </handler>
3399       <handler event="dragstart">
3400         event.stopPropagation();
3401       </handler>
3402     </handlers>
3403   </binding>
3405   <binding id="tabbrowser-tab" display="xul:hbox"
3406            extends="chrome://global/content/bindings/tabbox.xml#tab">
3407     <resources>
3408       <stylesheet src="chrome://browser/content/tabbrowser.css"/>
3409     </resources>
3411     <content context="tabContextMenu" closetabtext="&closeTab.label;">
3412       <xul:stack class="tab-stack" flex="1">
3413         <xul:hbox xbl:inherits="pinned,selected,titlechanged"
3414                   class="tab-background">
3415           <xul:hbox xbl:inherits="pinned,selected,titlechanged"
3416                     class="tab-background-start"/>
3417           <xul:hbox xbl:inherits="pinned,selected,titlechanged"
3418                     class="tab-background-middle"/>
3419           <xul:hbox xbl:inherits="pinned,selected,titlechanged"
3420                     class="tab-background-end"/>
3421         </xul:hbox>
3422         <xul:hbox xbl:inherits="pinned,selected,titlechanged"
3423                   class="tab-content" align="center">
3424           <xul:image xbl:inherits="fadein,pinned,busy,progress,selected"
3425                      class="tab-throbber"
3426                      role="presentation"/>
3427           <xul:image xbl:inherits="validate,src=image,fadein,pinned,selected"
3428                      class="tab-icon-image"
3429                      role="presentation"/>
3430           <xul:label flex="1"
3431                      xbl:inherits="value=label,crop,accesskey,fadein,pinned,selected"
3432                      class="tab-text tab-label"
3433                      role="presentation"/>
3434           <xul:toolbarbutton anonid="close-button"
3435                              xbl:inherits="fadein,pinned,selected"
3436                              tabindex="-1"
3437                              clickthrough="never"
3438                              class="tab-close-button"/>
3439         </xul:hbox>
3440       </xul:stack>
3441     </content>
3443     <implementation>
3444       <property name="pinned" readonly="true">
3445         <getter>
3446           return this.getAttribute("pinned") == "true";
3447         </getter>
3448       </property>
3449       <property name="hidden" readonly="true">
3450         <getter>
3451           return this.getAttribute("hidden") == "true";
3452         </getter>
3453       </property>
3455       <field name="mOverCloseButton">false</field>
3456       <field name="mCorrespondingMenuitem">null</field>
3457     </implementation>
3459     <handlers>
3460       <handler event="mouseover">
3461         var anonid = event.originalTarget.getAttribute("anonid");
3462         if (anonid == "close-button")
3463           this.mOverCloseButton = true;
3464       </handler>
3465       <handler event="mouseout">
3466         var anonid = event.originalTarget.getAttribute("anonid");
3467         if (anonid == "close-button")
3468           this.mOverCloseButton = false;
3469       </handler>
3470       <handler event="dragstart" phase="capturing">
3471         this.style.MozUserFocus = '';
3472       </handler>
3473       <handler event="mousedown" button="0" phase="capturing">
3474       <![CDATA[
3475         if (this.mOverCloseButton) {
3476           event.stopPropagation();
3477         }
3478         else {
3479           this.style.MozUserFocus = 'ignore';
3480           this.clientTop; // just using this to flush style updates
3481         }
3482       ]]>
3483       </handler>
3484       <handler event="mousedown" button="1">
3485         this.style.MozUserFocus = 'ignore';
3486         this.clientTop;
3487       </handler>
3488       <handler event="mousedown" button="2">
3489         this.style.MozUserFocus = 'ignore';
3490         this.clientTop;
3491       </handler>
3492       <handler event="mouseup">
3493         this.style.MozUserFocus = '';
3494       </handler>
3495     </handlers>
3496   </binding>
3498   <binding id="tabbrowser-alltabs-popup"
3499            extends="chrome://global/content/bindings/popup.xml#popup">
3500     <implementation implements="nsIDOMEventListener">
3501       <method name="_menuItemOnCommand">
3502         <parameter name="aEvent"/>
3503         <body><![CDATA[
3504           gBrowser.selectedTab = aEvent.target.tab;
3505         ]]></body>
3506       </method>
3508       <method name="_tabOnAttrModified">
3509         <parameter name="aEvent"/>
3510         <body><![CDATA[
3511           var tab = aEvent.target;
3512           this._setMenuitemAttributes(tab.mCorrespondingMenuitem, tab);
3513         ]]></body>
3514       </method>
3516       <method name="_tabOnTabClose">
3517         <parameter name="aEvent"/>
3518         <body><![CDATA[
3519           var menuItem = aEvent.target.mCorrespondingMenuitem;
3520           if (menuItem)
3521             this.removeChild(menuItem);
3522         ]]></body>
3523       </method>
3525       <method name="handleEvent">
3526         <parameter name="aEvent"/>
3527         <body><![CDATA[
3528           if (!aEvent.isTrusted)
3529             return;
3531           switch (aEvent.type) {
3532             case "command":
3533               this._menuItemOnCommand(aEvent);
3534               break;
3535             case "TabAttrModified":
3536               this._tabOnAttrModified(aEvent);
3537               break;
3538             case "TabClose":
3539               this._tabOnTabClose(aEvent);
3540               break;
3541             case "TabOpen":
3542               this._createTabMenuItem(aEvent.originalTarget);
3543               break;
3544             case "scroll":
3545               this._updateTabsVisibilityStatus();
3546               break;
3547           }
3548         ]]></body>
3549       </method>
3551       <method name="_updateTabsVisibilityStatus">
3552         <body><![CDATA[
3553           var tabContainer = gBrowser.tabContainer;
3554           // We don't want menu item decoration unless there is overflow.
3555           if (tabContainer.getAttribute("overflow") != "true")
3556             return;
3558           var tabstripBO = tabContainer.mTabstrip.scrollBoxObject;
3559           for (var i = 0; i < this.childNodes.length; i++) {
3560             var curTabBO = this.childNodes[i].tab.boxObject;
3561             if (curTabBO.screenX >= tabstripBO.screenX &&
3562                 curTabBO.screenX + curTabBO.width <= tabstripBO.screenX + tabstripBO.width)
3563               this.childNodes[i].setAttribute("tabIsVisible", "true"); 
3564             else
3565               this.childNodes[i].removeAttribute("tabIsVisible");
3566           }
3567         ]]></body>
3568       </method>
3570       <method name="_createTabMenuItem">
3571         <parameter name="aTab"/>
3572         <body><![CDATA[
3573           var menuItem = document.createElementNS(
3574             "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", 
3575             "menuitem");
3577           menuItem.setAttribute("class", "menuitem-iconic alltabs-item menuitem-with-favicon");
3579           this._setMenuitemAttributes(menuItem, aTab);
3581           // Keep some attributes of the menuitem in sync with its
3582           // corresponding tab (e.g. the tab label)
3583           aTab.mCorrespondingMenuitem = menuItem;
3584           menuItem.tab = aTab;
3585           menuItem.addEventListener("command", this, false);
3587           this.appendChild(menuItem);
3588           return menuItem;
3589         ]]></body>
3590       </method>
3592       <method name="_setMenuitemAttributes">
3593         <parameter name="aMenuitem"/>
3594         <parameter name="aTab"/>
3595         <body><![CDATA[
3596           aMenuitem.setAttribute("label", aTab.label);
3597           aMenuitem.setAttribute("crop", aTab.getAttribute("crop"));
3598           aMenuitem.setAttribute("image", aTab.getAttribute("image"));
3600           if (aTab.hasAttribute("busy"))
3601             aMenuitem.setAttribute("busy", aTab.getAttribute("busy"));
3602           else
3603             aMenuitem.removeAttribute("busy");
3605           if (aTab.selected)
3606             aMenuitem.setAttribute("selected", "true");
3607           else
3608             aMenuitem.removeAttribute("selected");
3609         ]]></body>
3610       </method>
3611     </implementation>
3613     <handlers>
3614       <handler event="popupshowing">
3615       <![CDATA[
3616         // set up the menu popup
3617         var tabcontainer = gBrowser.tabContainer;
3618         let tabs = gBrowser.visibleTabs;
3620         // Listen for changes in the tab bar.
3621         tabcontainer.addEventListener("TabOpen", this, false);
3622         tabcontainer.addEventListener("TabAttrModified", this, false);
3623         tabcontainer.addEventListener("TabClose", this, false);
3624         tabcontainer.mTabstrip.addEventListener("scroll", this, false);
3626         for (var i = 0; i < tabs.length; i++) {
3627           this._createTabMenuItem(tabs[i]);
3628         }
3629         this._updateTabsVisibilityStatus();
3630       ]]></handler>
3632       <handler event="popuphidden">
3633       <![CDATA[
3634         // clear out the menu popup and remove the listeners
3635         while (this.hasChildNodes()) {
3636           var menuItem = this.lastChild;
3637           menuItem.removeEventListener("command", this, false);
3638           menuItem.tab.mCorrespondingMenuitem = null;
3639           this.removeChild(menuItem);
3640         }
3641         var tabcontainer = gBrowser.tabContainer;
3642         tabcontainer.mTabstrip.removeEventListener("scroll", this, false);
3643         tabcontainer.removeEventListener("TabOpen", this, false);
3644         tabcontainer.removeEventListener("TabAttrModified", this, false);
3645         tabcontainer.removeEventListener("TabClose", this, false);
3646       ]]></handler>
3648       <handler event="DOMMenuItemActive">
3649       <![CDATA[
3650         var tab = event.target.tab;
3651         if (tab) {
3652           let overLink = tab.linkedBrowser.currentURI.spec;
3653           if (overLink == "about:blank")
3654             overLink = "";
3655           XULBrowserWindow.setOverLink(overLink, null);
3656         }
3657       ]]></handler>
3659       <handler event="DOMMenuItemInactive">
3660       <![CDATA[
3661         XULBrowserWindow.setOverLink("", null);
3662       ]]></handler>
3664     </handlers>
3665   </binding>
3667 </bindings>