Bumping manifests a=b2g-bump
[gecko.git] / browser / base / content / tabbrowser.xml
blobba93bf643c714ea10bd9d87dfde268e9c0909b01
1 <?xml version="1.0"?>
3 <!-- This Source Code Form is subject to the terms of the Mozilla Public
4    - License, v. 2.0. If a copy of the MPL was not distributed with this
5    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
7 <!DOCTYPE bindings [
8 <!ENTITY % tabBrowserDTD SYSTEM "chrome://browser/locale/tabbrowser.dtd" >
9 %tabBrowserDTD;
12 # MAKE_E10S_WORK surrounds code needed to have the front-end try to be smart
13 # about using non-remote browsers for loading certain URIs when remote tabs
14 # (browser.tabs.remote) are enabled.
15 #define MAKE_E10S_WORK 1
17 <bindings id="tabBrowserBindings"
18           xmlns="http://www.mozilla.org/xbl"
19           xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
20           xmlns:xbl="http://www.mozilla.org/xbl">
22   <binding id="tabbrowser">
23     <resources>
24       <stylesheet src="chrome://browser/content/tabbrowser.css"/>
25     </resources>
27     <content>
28       <xul:stringbundle anonid="tbstringbundle" src="chrome://browser/locale/tabbrowser.properties"/>
29       <xul:tabbox anonid="tabbox" class="tabbrowser-tabbox"
30                   flex="1" eventnode="document" xbl:inherits="handleCtrlPageUpDown"
31                   onselect="if (event.target.localName == 'tabpanels') this.parentNode.updateCurrentBrowser();">
32         <xul:tabpanels flex="1" class="plain" selectedIndex="0" anonid="panelcontainer">
33           <xul:notificationbox flex="1">
34             <xul:hbox flex="1" class="browserSidebarContainer">
35               <xul:vbox flex="1" class="browserContainer">
36                 <xul:stack flex="1" class="browserStack" anonid="browserStack">
37                   <xul:browser anonid="initialBrowser" type="content-primary" message="true" messagemanagergroup="browsers"
38                                xbl:inherits="tooltip=contenttooltip,contextmenu=contentcontextmenu,autocompletepopup,selectpopup"/>
39                 </xul:stack>
40               </xul:vbox>
41             </xul:hbox>
42           </xul:notificationbox>
43         </xul:tabpanels>
44       </xul:tabbox>
45       <children/>
46     </content>
47     <implementation implements="nsIDOMEventListener, nsIMessageListener">
49       <property name="tabContextMenu" readonly="true"
50                 onget="return this.tabContainer.contextMenu;"/>
52       <field name="tabContainer" readonly="true">
53         document.getElementById(this.getAttribute("tabcontainer"));
54       </field>
55       <field name="tabs" readonly="true">
56         this.tabContainer.childNodes;
57       </field>
59       <property name="visibleTabs" readonly="true">
60         <getter><![CDATA[
61           if (!this._visibleTabs)
62             this._visibleTabs = Array.filter(this.tabs,
63                                              function (tab) !tab.hidden && !tab.closing);
64           return this._visibleTabs;
65         ]]></getter>
66       </property>
68       <field name="closingTabsEnum" readonly="true">({ ALL: 0, OTHER: 1, TO_END: 2 });</field>
70       <field name="_visibleTabs">null</field>
72       <field name="mURIFixup" readonly="true">
73         Components.classes["@mozilla.org/docshell/urifixup;1"]
74                   .getService(Components.interfaces.nsIURIFixup);
75       </field>
76       <field name="mFaviconService" readonly="true">
77         Components.classes["@mozilla.org/browser/favicon-service;1"]
78                   .getService(Components.interfaces.nsIFaviconService);
79       </field>
80       <field name="_placesAutocomplete" readonly="true">
81          Components.classes["@mozilla.org/autocomplete/search;1?name=history"]
82                    .getService(Components.interfaces.mozIPlacesAutoComplete);
83       </field>
84       <field name="_unifiedComplete" readonly="true">
85          Components.classes["@mozilla.org/autocomplete/search;1?name=unifiedcomplete"]
86                    .getService(Components.interfaces.mozIPlacesAutoComplete);
87       </field>
88       <field name="PlacesUtils" readonly="true">
89         (Components.utils.import("resource://gre/modules/PlacesUtils.jsm", {})).PlacesUtils;
90       </field>
91       <field name="mTabBox" readonly="true">
92         document.getAnonymousElementByAttribute(this, "anonid", "tabbox");
93       </field>
94       <field name="mPanelContainer" readonly="true">
95         document.getAnonymousElementByAttribute(this, "anonid", "panelcontainer");
96       </field>
97       <field name="mStringBundle">
98         document.getAnonymousElementByAttribute(this, "anonid", "tbstringbundle");
99       </field>
100       <field name="mCurrentTab">
101         null
102       </field>
103       <field name="_lastRelatedTab">
104         null
105       </field>
106       <field name="mCurrentBrowser">
107         null
108       </field>
109       <field name="mProgressListeners">
110         []
111       </field>
112       <field name="mTabsProgressListeners">
113         []
114       </field>
115       <field name="mTabListeners">
116         []
117       </field>
118       <field name="mTabFilters">
119         []
120       </field>
121       <field name="mIsBusy">
122         false
123       </field>
124       <field name="arrowKeysShouldWrap" readonly="true">
125 #ifdef XP_MACOSX
126         true
127 #else
128         false
129 #endif
130       </field>
132       <field name="_autoScrollPopup">
133         null
134       </field>
136       <field name="_previewMode">
137         false
138       </field>
140       <field name="_lastFindValue">
141         ""
142       </field>
144       <property name="_numPinnedTabs" readonly="true">
145         <getter><![CDATA[
146           for (var i = 0; i < this.tabs.length; i++) {
147             if (!this.tabs[i].pinned)
148               break;
149           }
150           return i;
151         ]]></getter>
152       </property>
154       <property name="formValidationAnchor" readonly="true">
155         <getter><![CDATA[
156         if (this.mCurrentTab._formValidationAnchor) {
157           return this.mCurrentTab._formValidationAnchor;
158         }
159         let stack = this.mCurrentBrowser.parentNode;
160         // Create an anchor for the form validation popup
161         const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
162         let formValidationAnchor = document.createElementNS(NS_XUL, "hbox");
163         formValidationAnchor.className = "form-validation-anchor";
164         formValidationAnchor.hidden = true;
165         stack.appendChild(formValidationAnchor);
166         return this.mCurrentTab._formValidationAnchor = formValidationAnchor;
167         ]]></getter>
168       </property>
170       <method name="isFindBarInitialized">
171         <parameter name="aTab"/>
172         <body><![CDATA[
173           return (aTab || this.selectedTab)._findBar != undefined;
174         ]]></body>
175       </method>
177       <method name="getFindBar">
178         <parameter name="aTab"/>
179         <body><![CDATA[
180           if (!aTab)
181             aTab = this.selectedTab;
183           if (aTab._findBar)
184             return aTab._findBar;
186           let findBar = document.createElementNS(this.namespaceURI, "findbar");
187           let browser = this.getBrowserForTab(aTab);
188           let browserContainer = this.getBrowserContainer(browser);
189           browserContainer.appendChild(findBar);
191           // Force a style flush to ensure that our binding is attached.
192           findBar.clientTop;
194           findBar.browser = browser;
195           findBar._findField.value = this._lastFindValue;
197           aTab._findBar = findBar;
199           let event = document.createEvent("Events");
200           event.initEvent("TabFindInitialized", true, false);
201           aTab.dispatchEvent(event);
203           return findBar;
204         ]]></body>
205       </method>
207       <method name="getStatusPanel">
208         <body><![CDATA[
209           if (!this._statusPanel) {
210             this._statusPanel = document.createElementNS(this.namespaceURI, "statuspanel");
211             this._statusPanel.setAttribute("inactive", "true");
212             this._statusPanel.setAttribute("layer", "true");
213             this._appendStatusPanel();
214           }
215           return this._statusPanel;
216         ]]></body>
217       </method>
219       <method name="_appendStatusPanel">
220         <body><![CDATA[
221           if (this._statusPanel) {
222             let browser = this.selectedBrowser;
223             let browserContainer = this.getBrowserContainer(browser);
224             browserContainer.insertBefore(this._statusPanel, browser.parentNode.nextSibling);
225           }
226         ]]></body>
227       </method>
229       <method name="updateWindowResizers">
230         <body><![CDATA[
231           if (!window.gShowPageResizers)
232             return;
234           var show = window.windowState == window.STATE_NORMAL;
235           for (let i = 0; i < this.browsers.length; i++) {
236             this.browsers[i].showWindowResizer = show;
237           }
238         ]]></body>
239       </method>
241       <method name="_setCloseKeyState">
242         <parameter name="aEnabled"/>
243         <body><![CDATA[
244           let keyClose = document.getElementById("key_close");
245           let closeKeyEnabled = keyClose.getAttribute("disabled") != "true";
246           if (closeKeyEnabled == aEnabled)
247             return;
249           if (aEnabled)
250             keyClose.removeAttribute("disabled");
251           else
252             keyClose.setAttribute("disabled", "true");
254           // We also want to remove the keyboard shortcut from the file menu
255           // when the shortcut is disabled, and bring it back when it's
256           // renabled.
257           //
258           // Fixing bug 630826 could make that happen automatically.
259           // Fixing bug 630830 could avoid the ugly hack below.
261           let closeMenuItem = document.getElementById("menu_close");
262           let parentPopup = closeMenuItem.parentNode;
263           let nextItem = closeMenuItem.nextSibling;
264           let clonedItem = closeMenuItem.cloneNode(true);
266           parentPopup.removeChild(closeMenuItem);
268           if (aEnabled)
269             clonedItem.setAttribute("key", "key_close");
270           else
271             clonedItem.removeAttribute("key");
273           parentPopup.insertBefore(clonedItem, nextItem);
274         ]]></body>
275       </method>
277       <method name="pinTab">
278         <parameter name="aTab"/>
279         <body><![CDATA[
280           if (aTab.pinned)
281             return;
283           if (aTab.hidden)
284             this.showTab(aTab);
286           this.moveTabTo(aTab, this._numPinnedTabs);
287           aTab.setAttribute("pinned", "true");
288           this.tabContainer._unlockTabSizing();
289           this.tabContainer._positionPinnedTabs();
290           this.tabContainer.adjustTabstrip();
292           // Bug 961867 - [e10s] Implement the logic for app tabs
293           if (!gMultiProcessBrowser)
294             this.getBrowserForTab(aTab).docShell.isAppTab = true;
296           if (aTab.selected)
297             this._setCloseKeyState(false);
299           let event = document.createEvent("Events");
300           event.initEvent("TabPinned", true, false);
301           aTab.dispatchEvent(event);
302         ]]></body>
303       </method>
305       <method name="unpinTab">
306         <parameter name="aTab"/>
307         <body><![CDATA[
308           if (!aTab.pinned)
309             return;
311           this.moveTabTo(aTab, this._numPinnedTabs - 1);
312           aTab.removeAttribute("pinned");
313           aTab.style.MozMarginStart = "";
314           this.tabContainer._unlockTabSizing();
315           this.tabContainer._positionPinnedTabs();
316           this.tabContainer.adjustTabstrip();
318           // Bug 961867 - [e10s] Implement the logic for app tabs
319           if (!gMultiProcessBrowser)
320             this.getBrowserForTab(aTab).docShell.isAppTab = false;
322           if (aTab.selected)
323             this._setCloseKeyState(true);
325           let event = document.createEvent("Events");
326           event.initEvent("TabUnpinned", true, false);
327           aTab.dispatchEvent(event);
328         ]]></body>
329       </method>
331       <method name="previewTab">
332         <parameter name="aTab"/>
333         <parameter name="aCallback"/>
334         <body>
335           <![CDATA[
336             let currentTab = this.selectedTab;
337             try {
338               // Suppress focus, ownership and selected tab changes
339               this._previewMode = true;
340               this.selectedTab = aTab;
341               aCallback();
342             } finally {
343               this.selectedTab = currentTab;
344               this._previewMode = false;
345             }
346           ]]>
347         </body>
348       </method>
350       <method name="getBrowserAtIndex">
351         <parameter name="aIndex"/>
352         <body>
353           <![CDATA[
354             return this.browsers[aIndex];
355           ]]>
356         </body>
357       </method>
359       <method name="getBrowserIndexForDocument">
360         <parameter name="aDocument"/>
361         <body>
362           <![CDATA[
363             var tab = this._getTabForContentWindow(aDocument.defaultView);
364             return tab ? tab._tPos : -1;
365           ]]>
366         </body>
367       </method>
369       <method name="getBrowserForDocument">
370         <parameter name="aDocument"/>
371         <body>
372           <![CDATA[
373             var tab = this._getTabForContentWindow(aDocument.defaultView);
374             return tab ? tab.linkedBrowser : null;
375           ]]>
376         </body>
377       </method>
379       <method name="_getTabForContentWindow">
380         <parameter name="aWindow"/>
381         <body>
382         <![CDATA[
383           // When not using remote browsers, we can take a fast path by getting
384           // directly from the content window to the browser without looping
385           // over all browsers.
386           if (!gMultiProcessBrowser) {
387             let browser = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
388                                  .getInterface(Ci.nsIWebNavigation)
389                                  .QueryInterface(Ci.nsIDocShell)
390                                  .chromeEventHandler;
391             return this._getTabForBrowser(browser);
392           }
394           for (let i = 0; i < this.browsers.length; i++) {
395             if (this.browsers[i].contentWindow == aWindow)
396               return this.tabs[i];
397           }
398           return null;
399         ]]>
400         </body>
401       </method>
403       <method name="_getTabForBrowser">
404         <parameter name="aBrowser"/>
405         <body>
406         <![CDATA[
407           for (let i = 0; i < this.tabs.length; i++) {
408             if (this.tabs[i].linkedBrowser == aBrowser)
409               return this.tabs[i];
410           }
411           return null;
412         ]]>
413         </body>
414       </method>
416       <method name="getNotificationBox">
417         <parameter name="aBrowser"/>
418         <body>
419           <![CDATA[
420             return this.getSidebarContainer(aBrowser).parentNode;
421           ]]>
422         </body>
423       </method>
425       <method name="getSidebarContainer">
426         <parameter name="aBrowser"/>
427         <body>
428           <![CDATA[
429             return this.getBrowserContainer(aBrowser).parentNode;
430           ]]>
431         </body>
432       </method>
434       <method name="getBrowserContainer">
435         <parameter name="aBrowser"/>
436         <body>
437           <![CDATA[
438             return (aBrowser || this.mCurrentBrowser).parentNode.parentNode;
439           ]]>
440         </body>
441       </method>
443       <method name="getTabModalPromptBox">
444         <parameter name="aBrowser"/>
445         <body>
446           <![CDATA[
447             const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
448             let browser = (aBrowser || this.mCurrentBrowser);
449             let stack = browser.parentNode;
450             let self = this;
452             let promptBox = {
453               appendPrompt : function(args, onCloseCallback) {
454                 let newPrompt = document.createElementNS(XUL_NS, "tabmodalprompt");
455                 stack.appendChild(newPrompt);
456                 browser.setAttribute("tabmodalPromptShowing", true);
458                 newPrompt.clientTop; // style flush to assure binding is attached
460                 let tab = self._getTabForBrowser(browser);
461                 newPrompt.init(args, tab, onCloseCallback);
462                 return newPrompt;
463               },
465               removePrompt : function(aPrompt) {
466                 stack.removeChild(aPrompt);
468                 let prompts = this.listPrompts();
469                 if (prompts.length) {
470                   let prompt = prompts[prompts.length - 1];
471                   prompt.Dialog.setDefaultFocus();
472                 } else {
473                   browser.removeAttribute("tabmodalPromptShowing");
474                   browser.focus();
475                 }
476               },
478               listPrompts : function(aPrompt) {
479                 let els = stack.getElementsByTagNameNS(XUL_NS, "tabmodalprompt");
480                 // NodeList --> real JS array
481                 let prompts = Array.slice(els);
482                 return prompts;
483               },
484             };
486             return promptBox;
487           ]]>
488         </body>
489       </method>
491       <method name="_callProgressListeners">
492         <parameter name="aBrowser"/>
493         <parameter name="aMethod"/>
494         <parameter name="aArguments"/>
495         <parameter name="aCallGlobalListeners"/>
496         <parameter name="aCallTabsListeners"/>
497         <body><![CDATA[
498           var rv = true;
500           if (!aBrowser)
501             aBrowser = this.mCurrentBrowser;
503           if (aCallGlobalListeners != false &&
504               aBrowser == this.mCurrentBrowser) {
505             this.mProgressListeners.forEach(function (p) {
506               if (aMethod in p) {
507                 try {
508                   if (!p[aMethod].apply(p, aArguments))
509                     rv = false;
510                 } catch (e) {
511                   // don't inhibit other listeners
512                   Components.utils.reportError(e);
513                 }
514               }
515             });
516           }
518           if (aCallTabsListeners != false) {
519             aArguments.unshift(aBrowser);
521             this.mTabsProgressListeners.forEach(function (p) {
522               if (aMethod in p) {
523                 try {
524                   if (!p[aMethod].apply(p, aArguments))
525                     rv = false;
526                 } catch (e) {
527                   // don't inhibit other listeners
528                   Components.utils.reportError(e);
529                 }
530               }
531             });
532           }
534           return rv;
535         ]]></body>
536       </method>
538       <!-- A web progress listener object definition for a given tab. -->
539       <method name="mTabProgressListener">
540         <parameter name="aTab"/>
541         <parameter name="aBrowser"/>
542         <parameter name="aStartsBlank"/>
543         <body>
544         <![CDATA[
545           return ({
546             mTabBrowser: this,
547             mTab: aTab,
548             mBrowser: aBrowser,
549             mBlank: aStartsBlank,
551             // cache flags for correct status UI update after tab switching
552             mStateFlags: 0,
553             mStatus: 0,
554             mMessage: "",
555             mTotalProgress: 0,
557             // count of open requests (should always be 0 or 1)
558             mRequestCount: 0,
560             destroy: function () {
561               delete this.mTab;
562               delete this.mBrowser;
563               delete this.mTabBrowser;
564             },
566             _callProgressListeners: function () {
567               Array.unshift(arguments, this.mBrowser);
568               return this.mTabBrowser._callProgressListeners.apply(this.mTabBrowser, arguments);
569             },
571             _shouldShowProgress: function (aRequest) {
572               if (this.mBlank)
573                 return false;
575               if (gMultiProcessBrowser)
576                 return true;
578               // Don't show progress indicators in tabs for about: URIs
579               // pointing to local resources.
580               try {
581                 let channel = aRequest.QueryInterface(Ci.nsIChannel);
582                 if (channel.originalURI.schemeIs("about") &&
583                     (channel.URI.schemeIs("jar") || channel.URI.schemeIs("file")))
584                   return false;
585               } catch (e) {}
587               return true;
588             },
590             onProgressChange: function (aWebProgress, aRequest,
591                                         aCurSelfProgress, aMaxSelfProgress,
592                                         aCurTotalProgress, aMaxTotalProgress) {
593               this.mTotalProgress = aMaxTotalProgress ? aCurTotalProgress / aMaxTotalProgress : 0;
595               if (!this._shouldShowProgress(aRequest))
596                 return;
598               if (this.mTotalProgress)
599                 this.mTab.setAttribute("progress", "true");
601               this._callProgressListeners("onProgressChange",
602                                           [aWebProgress, aRequest,
603                                            aCurSelfProgress, aMaxSelfProgress,
604                                            aCurTotalProgress, aMaxTotalProgress]);
605             },
607             onProgressChange64: function (aWebProgress, aRequest,
608                                           aCurSelfProgress, aMaxSelfProgress,
609                                           aCurTotalProgress, aMaxTotalProgress) {
610               return this.onProgressChange(aWebProgress, aRequest,
611                 aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress,
612                 aMaxTotalProgress);
613             },
615             onStateChange: function (aWebProgress, aRequest, aStateFlags, aStatus) {
616               if (!aRequest)
617                 return;
619               var oldBlank = this.mBlank;
621               const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
622               const nsIChannel = Components.interfaces.nsIChannel;
624               if (aStateFlags & nsIWebProgressListener.STATE_START) {
625                 this.mRequestCount++;
626               }
627               else if (aStateFlags & nsIWebProgressListener.STATE_STOP) {
628                 const NS_ERROR_UNKNOWN_HOST = 2152398878;
629                 if (--this.mRequestCount > 0 && aStatus == NS_ERROR_UNKNOWN_HOST) {
630                   // to prevent bug 235825: wait for the request handled
631                   // by the automatic keyword resolver
632                   return;
633                 }
634                 // since we (try to) only handle STATE_STOP of the last request,
635                 // the count of open requests should now be 0
636                 this.mRequestCount = 0;
637               }
639               if (aStateFlags & nsIWebProgressListener.STATE_START &&
640                   aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
641                 // It's okay to clear what the user typed when we start
642                 // loading a document. If the user types, this counter gets
643                 // set to zero, if the document load ends without an
644                 // onLocationChange, this counter gets decremented
645                 // (so we keep it while switching tabs after failed loads)
646                 // We need to add 2 because loadURIWithFlags may have
647                 // cancelled a pending load which would have cleared
648                 // its anchor scroll detection temporary increment.
649                 if (aWebProgress.isTopLevel)
650                   this.mBrowser.userTypedClear += 2;
652                 if (this._shouldShowProgress(aRequest)) {
653                   if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) {
654                     this.mTab.setAttribute("busy", "true");
655                     if (!(aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_RELOAD))
656                       this.mTabBrowser.setTabTitleLoading(this.mTab);
657                   }
659                   if (this.mTab.selected)
660                     this.mTabBrowser.mIsBusy = true;
661                 }
662               }
663               else if (aStateFlags & nsIWebProgressListener.STATE_STOP &&
664                        aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
666                 if (this.mTab.hasAttribute("busy")) {
667                   this.mTab.removeAttribute("busy");
668                   this.mTabBrowser._tabAttrModified(this.mTab);
669                   if (!this.mTab.selected)
670                     this.mTab.setAttribute("unread", "true");
671                 }
672                 this.mTab.removeAttribute("progress");
674                 if (aWebProgress.isTopLevel) {
675                   if (!Components.isSuccessCode(aStatus) &&
676                       !isTabEmpty(this.mTab)) {
677                     // Restore the current document's location in case the
678                     // request was stopped (possibly from a content script)
679                     // before the location changed.
681                     this.mBrowser.userTypedValue = null;
683                     if (this.mTab.selected && gURLBar)
684                       URLBarSetURI();
685                   } else {
686                     // The document is done loading, we no longer want the
687                     // value cleared.
689                     if (this.mBrowser.userTypedClear > 1)
690                       this.mBrowser.userTypedClear -= 2;
691                     else if (this.mBrowser.userTypedClear > 0)
692                       this.mBrowser.userTypedClear--;
693                   }
695                   if (!this.mBrowser.mIconURL)
696                     this.mTabBrowser.useDefaultIcon(this.mTab);
697                 }
699                 if (this.mBlank)
700                   this.mBlank = false;
702                 var location = aRequest.QueryInterface(nsIChannel).URI;
704                 // For keyword URIs clear the user typed value since they will be changed into real URIs
705                 if (location.scheme == "keyword")
706                   this.mBrowser.userTypedValue = null;
708                 if (this.mTab.label == this.mTabBrowser.mStringBundle.getString("tabs.connecting"))
709                   this.mTabBrowser.setTabTitle(this.mTab);
711                 if (this.mTab.selected)
712                   this.mTabBrowser.mIsBusy = false;
713               }
715               if (oldBlank) {
716                 this._callProgressListeners("onUpdateCurrentBrowser",
717                                             [aStateFlags, aStatus, "", 0],
718                                             true, false);
719               } else {
720                 this._callProgressListeners("onStateChange",
721                                             [aWebProgress, aRequest, aStateFlags, aStatus],
722                                             true, false);
723               }
725               this._callProgressListeners("onStateChange",
726                                           [aWebProgress, aRequest, aStateFlags, aStatus],
727                                           false);
729               if (aStateFlags & (nsIWebProgressListener.STATE_START |
730                                  nsIWebProgressListener.STATE_STOP)) {
731                 // reset cached temporary values at beginning and end
732                 this.mMessage = "";
733                 this.mTotalProgress = 0;
734               }
735               this.mStateFlags = aStateFlags;
736               this.mStatus = aStatus;
737             },
739             onLocationChange: function (aWebProgress, aRequest, aLocation,
740                                         aFlags) {
741               // OnLocationChange is called for both the top-level content
742               // and the subframes.
743               let topLevel = aWebProgress.isTopLevel;
745               if (topLevel) {
746                 // If userTypedClear > 0, the document loaded correctly and we should be
747                 // clearing the user typed value. We also need to clear the typed value
748                 // if the document failed to load, to make sure the urlbar reflects the
749                 // failed URI (particularly for SSL errors). However, don't clear the value
750                 // if the error page's URI is about:blank, because that causes complete
751                 // loss of urlbar contents for invalid URI errors (see bug 867957).
752                 if (this.mBrowser.userTypedClear > 0 ||
753                     ((aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) &&
754                      aLocation.spec != "about:blank"))
755                   this.mBrowser.userTypedValue = null;
757                 // Clear out the missing plugins list since it's related to the
758                 // previous location.
759                 this.mBrowser.missingPlugins = null;
761                 if (this.mTabBrowser.isFindBarInitialized(this.mTab)) {
762                   let findBar = this.mTabBrowser.getFindBar(this.mTab);
764                   // Close the Find toolbar if we're in old-style TAF mode
765                   if (findBar.findMode != findBar.FIND_NORMAL)
766                     findBar.close();
768                   // fix bug 253793 - turn off highlight when page changes
769                   findBar.getElement("highlight").checked = false;
770                 }
772                 // Don't clear the favicon if this onLocationChange was
773                 // triggered by a pushState or a replaceState.  See bug 550565.
774                 if (aWebProgress.isLoadingDocument &&
775                     !(aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_PUSHSTATE)) {
776                   this.mBrowser.mIconURL = null;
777                 }
779                 let autocomplete = this.mTabBrowser._placesAutocomplete;
780                 let unifiedComplete = this.mTabBrowser._unifiedComplete;
781                 if (this.mBrowser.registeredOpenURI) {
782                   autocomplete.unregisterOpenPage(this.mBrowser.registeredOpenURI);
783                   unifiedComplete.unregisterOpenPage(this.mBrowser.registeredOpenURI);
784                   delete this.mBrowser.registeredOpenURI;
785                 }
786                 // Tabs in private windows aren't registered as "Open" so
787                 // that they don't appear as switch-to-tab candidates.
788                 if (!isBlankPageURL(aLocation.spec) &&
789                     (!PrivateBrowsingUtils.isWindowPrivate(window) ||
790                     PrivateBrowsingUtils.permanentPrivateBrowsing)) {
791                   autocomplete.registerOpenPage(aLocation);
792                   unifiedComplete.registerOpenPage(aLocation);
793                   this.mBrowser.registeredOpenURI = aLocation;
794                 }
795               }
797               if (!this.mBlank) {
798                 this._callProgressListeners("onLocationChange",
799                                             [aWebProgress, aRequest, aLocation,
800                                              aFlags]);
801               }
803               if (topLevel) {
804                 this.mBrowser.lastURI = aLocation;
805                 this.mBrowser.lastLocationChange = Date.now();
806               }
807             },
809             onStatusChange: function (aWebProgress, aRequest, aStatus, aMessage) {
810               if (this.mBlank)
811                 return;
813               this._callProgressListeners("onStatusChange",
814                                           [aWebProgress, aRequest, aStatus, aMessage]);
816               this.mMessage = aMessage;
817             },
819             onSecurityChange: function (aWebProgress, aRequest, aState) {
820               this._callProgressListeners("onSecurityChange",
821                                           [aWebProgress, aRequest, aState]);
822             },
824             onRefreshAttempted: function (aWebProgress, aURI, aDelay, aSameURI) {
825               return this._callProgressListeners("onRefreshAttempted",
826                                                  [aWebProgress, aURI, aDelay, aSameURI]);
827             },
829             QueryInterface: function (aIID) {
830               if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
831                   aIID.equals(Components.interfaces.nsIWebProgressListener2) ||
832                   aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
833                   aIID.equals(Components.interfaces.nsISupports))
834                 return this;
835               throw Components.results.NS_NOINTERFACE;
836             }
837           });
838         ]]>
839         </body>
840       </method>
842       <method name="setIcon">
843         <parameter name="aTab"/>
844         <parameter name="aURI"/>
845         <body>
846           <![CDATA[
847             var browser = this.getBrowserForTab(aTab);
848             browser.mIconURL = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
850             if (aURI && this.mFaviconService) {
851               if (!(aURI instanceof Ci.nsIURI))
852                 aURI = makeURI(aURI);
853               this.mFaviconService.setAndFetchFaviconForPage(browser.currentURI,
854                                                              aURI, false,
855                                                              PrivateBrowsingUtils.isWindowPrivate(window) ?
856                                                                this.mFaviconService.FAVICON_LOAD_PRIVATE :
857                                                                this.mFaviconService.FAVICON_LOAD_NON_PRIVATE);
858             }
860             let sizedIconUrl = browser.mIconURL || "";
861             if (sizedIconUrl) {
862               sizedIconUrl = this.PlacesUtils.getImageURLForResolution(window, sizedIconUrl);
863             }
864             if (sizedIconUrl != aTab.getAttribute("image")) {
865               if (sizedIconUrl)
866                 aTab.setAttribute("image", sizedIconUrl);
867               else
868                 aTab.removeAttribute("image");
869               this._tabAttrModified(aTab);
870             }
872             this._callProgressListeners(browser, "onLinkIconAvailable", [browser.mIconURL]);
873           ]]>
874         </body>
875       </method>
877       <method name="getIcon">
878         <parameter name="aTab"/>
879         <body>
880           <![CDATA[
881             let browser = aTab ? this.getBrowserForTab(aTab) : this.selectedBrowser;
882             return browser.mIconURL;
883           ]]>
884         </body>
885       </method>
887       <method name="shouldLoadFavIcon">
888         <parameter name="aURI"/>
889         <body>
890           <![CDATA[
891             return (aURI &&
892                     Services.prefs.getBoolPref("browser.chrome.site_icons") &&
893                     Services.prefs.getBoolPref("browser.chrome.favicons") &&
894                     ("schemeIs" in aURI) && (aURI.schemeIs("http") || aURI.schemeIs("https")));
895           ]]>
896         </body>
897       </method>
899       <method name="useDefaultIcon">
900         <parameter name="aTab"/>
901         <body>
902           <![CDATA[
903             var browser = this.getBrowserForTab(aTab);
904             var documentURI = browser.documentURI;
905             var icon = null;
907             if (browser.imageDocument) {
908               if (Services.prefs.getBoolPref("browser.chrome.site_icons")) {
909                 let sz = Services.prefs.getIntPref("browser.chrome.image_icons.max_size");
910                 if (browser.imageDocument.width <= sz &&
911                     browser.imageDocument.height <= sz) {
912                   icon = browser.currentURI;
913                 }
914               }
915             }
917             // Use documentURIObject in the check for shouldLoadFavIcon so that we
918             // do the right thing with about:-style error pages.  Bug 453442
919             if (!icon && this.shouldLoadFavIcon(documentURI)) {
920               let url = documentURI.prePath + "/favicon.ico";
921               if (!this.isFailedIcon(url))
922                 icon = url;
923             }
924             this.setIcon(aTab, icon);
925           ]]>
926         </body>
927       </method>
929       <method name="isFailedIcon">
930         <parameter name="aURI"/>
931         <body>
932           <![CDATA[
933             if (this.mFaviconService) {
934               if (!(aURI instanceof Ci.nsIURI))
935                 aURI = makeURI(aURI);
936               return this.mFaviconService.isFailedFavicon(aURI);
937             }
938             return null;
939           ]]>
940         </body>
941       </method>
943       <method name="getWindowTitleForBrowser">
944         <parameter name="aBrowser"/>
945         <body>
946           <![CDATA[
947             var newTitle = "";
948             var docElement = this.ownerDocument.documentElement;
949             var sep = docElement.getAttribute("titlemenuseparator");
951             // Strip out any null bytes in the content title, since the
952             // underlying widget implementations of nsWindow::SetTitle pass
953             // null-terminated strings to system APIs.
954             var docTitle = aBrowser.contentTitle.replace("\0", "", "g");
956             if (!docTitle)
957               docTitle = docElement.getAttribute("titledefault");
959             var modifier = docElement.getAttribute("titlemodifier");
960             if (docTitle) {
961               newTitle += docElement.getAttribute("titlepreface");
962               newTitle += docTitle;
963               if (modifier)
964                 newTitle += sep;
965             }
966             newTitle += modifier;
968             // If location bar is hidden and the URL type supports a host,
969             // add the scheme and host to the title to prevent spoofing.
970             // XXX https://bugzilla.mozilla.org/show_bug.cgi?id=22183#c239
971             try {
972               if (docElement.getAttribute("chromehidden").contains("location")) {
973                 var uri = this.mURIFixup.createExposableURI(
974                             aBrowser.currentURI);
975                 if (uri.scheme == "about")
976                   newTitle = uri.spec + sep + newTitle;
977                 else
978                   newTitle = uri.prePath + sep + newTitle;
979               }
980             } catch (e) {}
982             return newTitle;
983           ]]>
984         </body>
985       </method>
987       <method name="updateTitlebar">
988         <body>
989           <![CDATA[
990             if ("TabView" in window && TabView.isVisible()) {
991               // ToDo: this will be removed when we gain ability to draw to the menu bar.
992               // Bug 586175
993               this.ownerDocument.title = TabView.windowTitle;
994             }
995             else {
996               this.ownerDocument.title = this.getWindowTitleForBrowser(this.mCurrentBrowser);
997             }
998           ]]>
999         </body>
1000       </method>
1002       <method name="updateCurrentBrowser">
1003         <parameter name="aForceUpdate"/>
1004         <body>
1005           <![CDATA[
1006             var newBrowser = this.getBrowserAtIndex(this.tabContainer.selectedIndex);
1007             if (this.mCurrentBrowser == newBrowser && !aForceUpdate)
1008               return;
1010             if (!aForceUpdate) {
1011               TelemetryStopwatch.start("FX_TAB_SWITCH_UPDATE_MS");
1012               window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils)
1013                                                              .beginTabSwitch();
1014             }
1016             var oldTab = this.mCurrentTab;
1018             // Preview mode should not reset the owner
1019             if (!this._previewMode && !oldTab.selected)
1020               oldTab.owner = null;
1022             if (this._lastRelatedTab) {
1023               if (!this._lastRelatedTab.selected)
1024                 this._lastRelatedTab.owner = null;
1025               this._lastRelatedTab = null;
1026             }
1028             var oldBrowser = this.mCurrentBrowser;
1030             if (!gMultiProcessBrowser) {
1031               oldBrowser.setAttribute("type", "content-targetable");
1032               oldBrowser.docShellIsActive = false;
1033               newBrowser.setAttribute("type", "content-primary");
1034               newBrowser.docShellIsActive =
1035                 (window.windowState != window.STATE_MINIMIZED);
1036             }
1038             var updateBlockedPopups = false;
1039             if ((oldBrowser.blockedPopups && !newBrowser.blockedPopups) ||
1040                 (!oldBrowser.blockedPopups && newBrowser.blockedPopups))
1041               updateBlockedPopups = true;
1043             this.mCurrentBrowser = newBrowser;
1044             this.mCurrentTab = this.tabContainer.selectedItem;
1045             this.showTab(this.mCurrentTab);
1047             var forwardButtonContainer = document.getElementById("urlbar-wrapper");
1048             if (forwardButtonContainer) {
1049               forwardButtonContainer.setAttribute("switchingtabs", "true");
1050               window.addEventListener("MozAfterPaint", function removeSwitchingtabsAttr() {
1051                 window.removeEventListener("MozAfterPaint", removeSwitchingtabsAttr);
1052                 forwardButtonContainer.removeAttribute("switchingtabs");
1053               });
1054             }
1056             this._appendStatusPanel();
1058             if (updateBlockedPopups)
1059               this.mCurrentBrowser.updateBlockedPopups();
1061             // Update the URL bar.
1062             var loc = this.mCurrentBrowser.currentURI;
1064             // Bug 666809 - SecurityUI support for e10s
1065             var webProgress = this.mCurrentBrowser.webProgress;
1066             var securityUI = this.mCurrentBrowser.securityUI;
1068             this._callProgressListeners(null, "onLocationChange",
1069                                         [webProgress, null, loc, 0], true,
1070                                         false);
1072             if (securityUI) {
1073               this._callProgressListeners(null, "onSecurityChange",
1074                                           [webProgress, null, securityUI.state], true, false);
1075             }
1077             var listener = this.mTabListeners[this.tabContainer.selectedIndex] || null;
1078             if (listener && listener.mStateFlags) {
1079               this._callProgressListeners(null, "onUpdateCurrentBrowser",
1080                                           [listener.mStateFlags, listener.mStatus,
1081                                            listener.mMessage, listener.mTotalProgress],
1082                                           true, false);
1083             }
1085             if (!this._previewMode) {
1086               this.mCurrentTab.removeAttribute("unread");
1087               oldTab.lastAccessed = Date.now();
1089               let oldFindBar = oldTab._findBar;
1090               if (oldFindBar &&
1091                   oldFindBar.findMode == oldFindBar.FIND_NORMAL &&
1092                   !oldFindBar.hidden)
1093                 this._lastFindValue = oldFindBar._findField.value;
1095               this.updateTitlebar();
1097               this.mCurrentTab.removeAttribute("titlechanged");
1098             }
1100             // If the new tab is busy, and our current state is not busy, then
1101             // we need to fire a start to all progress listeners.
1102             const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
1103             if (this.mCurrentTab.hasAttribute("busy") && !this.mIsBusy) {
1104               this.mIsBusy = true;
1105               this._callProgressListeners(null, "onStateChange",
1106                                           [webProgress, null,
1107                                            nsIWebProgressListener.STATE_START |
1108                                            nsIWebProgressListener.STATE_IS_NETWORK, 0],
1109                                           true, false);
1110             }
1112             // If the new tab is not busy, and our current state is busy, then
1113             // we need to fire a stop to all progress listeners.
1114             if (!this.mCurrentTab.hasAttribute("busy") && this.mIsBusy) {
1115               this.mIsBusy = false;
1116               this._callProgressListeners(null, "onStateChange",
1117                                           [webProgress, null,
1118                                            nsIWebProgressListener.STATE_STOP |
1119                                            nsIWebProgressListener.STATE_IS_NETWORK, 0],
1120                                           true, false);
1121             }
1123             this._setCloseKeyState(!this.mCurrentTab.pinned);
1125             // TabSelect events are suppressed during preview mode to avoid confusing extensions and other bits of code
1126             // that might rely upon the other changes suppressed.
1127             // Focus is suppressed in the event that the main browser window is minimized - focusing a tab would restore the window
1128             if (!this._previewMode) {
1129               // We've selected the new tab, so go ahead and notify listeners.
1130               let event = new CustomEvent("TabSelect", {
1131                 bubbles: true,
1132                 cancelable: false,
1133                 detail: {
1134                   previousTab: oldTab
1135                 }
1136               });
1137               this.mCurrentTab.dispatchEvent(event);
1139               this._tabAttrModified(oldTab);
1140               this._tabAttrModified(this.mCurrentTab);
1142               if (oldBrowser != newBrowser &&
1143                   oldBrowser.docShell &&
1144                   oldBrowser.docShell.contentViewer.inPermitUnload) {
1145                 // Since the user is switching away from a tab that has
1146                 // a beforeunload prompt active, we remove the prompt.
1147                 // This prevents confusing user flows like the following:
1148                 //   1. User attempts to close Firefox
1149                 //   2. User switches tabs (ingoring a beforeunload prompt)
1150                 //   3. User returns to tab, presses "Leave page"
1151                 let promptBox = this.getTabModalPromptBox(oldBrowser);
1152                 let prompts = promptBox.listPrompts();
1153                 // There might not be any prompts here if the tab was closed
1154                 // while in an onbeforeunload prompt, which will have
1155                 // destroyed aforementioned prompt already, so check there's
1156                 // something to remove, first:
1157                 if (prompts.length) {
1158                   // NB: This code assumes that the beforeunload prompt
1159                   //     is the top-most prompt on the tab.
1160                   promptBox.removePrompt(prompts[prompts.length - 1]);
1161                 }
1162               }
1164               if (!gMultiProcessBrowser)
1165                 this._adjustFocusAfterTabSwitch(this.mCurrentTab, oldTab);
1166             }
1168             this.tabContainer._setPositionalAttributes();
1170             if (!aForceUpdate)
1171               TelemetryStopwatch.finish("FX_TAB_SWITCH_UPDATE_MS");
1172           ]]>
1173         </body>
1174       </method>
1176       <method name="_adjustFocusAfterTabSwitch">
1177         <parameter name="newTab"/>
1178         <parameter name="oldTab"/>
1179         <body><![CDATA[
1180         let newBrowser = this.getBrowserForTab(newTab);
1181         let oldBrowser = this.getBrowserForTab(oldTab);
1183         if (oldBrowser) {
1184           oldBrowser._urlbarFocused = (gURLBar && gURLBar.focused);
1185           if (this.isFindBarInitialized(oldTab)) {
1186             let findBar = this.getFindBar(oldTab);
1187             oldTab._findBarFocused = (!findBar.hidden &&
1188               findBar._findField.getAttribute("focused") == "true");
1189           }
1190         }
1192         // When focus is in the tab bar, retain it there.
1193         if (document.activeElement == oldTab) {
1194           // We need to explicitly focus the new tab, because
1195           // tabbox.xml does this only in some cases.
1196           this.mCurrentTab.focus();
1197           return;
1198         }
1200         // If there's a tabmodal prompt showing, focus it.
1201         if (newBrowser.hasAttribute("tabmodalPromptShowing")) {
1202           let XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
1203           let prompts = newBrowser.parentNode.getElementsByTagNameNS(XUL_NS, "tabmodalprompt");
1204           let prompt = prompts[prompts.length - 1];
1205           prompt.Dialog.setDefaultFocus();
1206           return;
1207         }
1209         // Focus the location bar if it was previously focused for that tab.
1210         // In full screen mode, only bother making the location bar visible
1211         // if the tab is a blank one.
1212         if (newBrowser._urlbarFocused && gURLBar) {
1214           // Explicitly close the popup if the URL bar retains focus
1215           gURLBar.closePopup();
1217           if (!window.fullScreen) {
1218             gURLBar.focus();
1219             return;
1220           }
1222           if (isTabEmpty(this.mCurrentTab)) {
1223             focusAndSelectUrlBar();
1224             return;
1225           }
1226         }
1228         // Focus the find bar if it was previously focused for that tab.
1229         if (gFindBarInitialized && !gFindBar.hidden &&
1230             this.selectedTab._findBarFocused) {
1231           gFindBar._findField.focus();
1232           return;
1233         }
1235         // Otherwise, focus the content area. If we're not using remote tabs, we
1236         // can focus the content area right away, since tab switching is synchronous.
1237         // If we're using remote tabs, we have to wait until after we've finalized
1238         // switching the tabs.
1240         if (newTab._skipContentFocus) {
1241           // It's possible the tab we're switching to is ready to focus asynchronously,
1242           // when we've already focused something else. In that case, this
1243           // _skipContentFocus property can be set so that we skip focusing the
1244           // content after we switch tabs.
1245           delete newTab._skipContentFocus;
1246           return;
1247         }
1249         let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
1250         let focusFlags = fm.FLAG_NOSCROLL;
1252         if (!gMultiProcessBrowser) {
1253           let newFocusedElement = fm.getFocusedElementForWindow(window.content, true, {});
1255           // for anchors, use FLAG_SHOWRING so that it is clear what link was
1256           // last clicked when switching back to that tab
1257           if (newFocusedElement &&
1258               (newFocusedElement instanceof HTMLAnchorElement ||
1259                newFocusedElement.getAttributeNS("http://www.w3.org/1999/xlink", "type") == "simple"))
1260             focusFlags |= fm.FLAG_SHOWRING;
1261         }
1263         fm.setFocus(newBrowser, focusFlags);
1264         ]]></body>
1265       </method>
1267       <method name="_tabAttrModified">
1268         <parameter name="aTab"/>
1269         <body><![CDATA[
1270           if (aTab.closing)
1271             return;
1273           // This event should be dispatched when any of these attributes change:
1274           // label, crop, busy, image, selected
1275           var event = document.createEvent("Events");
1276           event.initEvent("TabAttrModified", true, false);
1277           aTab.dispatchEvent(event);
1278         ]]></body>
1279       </method>
1281       <method name="setTabTitleLoading">
1282         <parameter name="aTab"/>
1283         <body>
1284           <![CDATA[
1285             aTab.label = this.mStringBundle.getString("tabs.connecting");
1286             aTab.crop = "end";
1287             this._tabAttrModified(aTab);
1288           ]]>
1289         </body>
1290       </method>
1292       <method name="setTabTitle">
1293         <parameter name="aTab"/>
1294         <body>
1295           <![CDATA[
1296             var browser = this.getBrowserForTab(aTab);
1297             var crop = "end";
1298             var title = browser.contentTitle;
1300             if (!title) {
1301               if (browser.currentURI.spec) {
1302                 try {
1303                   title = this.mURIFixup.createExposableURI(browser.currentURI).spec;
1304                 } catch(ex) {
1305                   title = browser.currentURI.spec;
1306                 }
1307               }
1309               if (title && !isBlankPageURL(title)) {
1310                 // At this point, we now have a URI.
1311                 // Let's try to unescape it using a character set
1312                 // in case the URI is not ASCII.
1313                 try {
1314                   var characterSet = browser.characterSet;
1315                   const textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"]
1316                                                  .getService(Components.interfaces.nsITextToSubURI);
1317                   title = textToSubURI.unEscapeNonAsciiURI(characterSet, title);
1318                 } catch(ex) { /* Do nothing. */ }
1320                 crop = "center";
1322               } else // Still no title?  Fall back to our untitled string.
1323                 title = this.mStringBundle.getString("tabs.emptyTabTitle");
1324             }
1326             if (aTab.label == title &&
1327                 aTab.crop == crop)
1328               return false;
1330             aTab.label = title;
1331             aTab.crop = crop;
1332             this._tabAttrModified(aTab);
1334             if (aTab.selected)
1335               this.updateTitlebar();
1337             return true;
1338           ]]>
1339         </body>
1340       </method>
1342       <method name="loadOneTab">
1343         <parameter name="aURI"/>
1344         <parameter name="aReferrerURI"/>
1345         <parameter name="aCharset"/>
1346         <parameter name="aPostData"/>
1347         <parameter name="aLoadInBackground"/>
1348         <parameter name="aAllowThirdPartyFixup"/>
1349         <body>
1350           <![CDATA[
1351             var aFromExternal;
1352             var aRelatedToCurrent;
1353             var aAllowMixedContent;
1354             var aSkipAnimation;
1355             if (arguments.length == 2 &&
1356                 typeof arguments[1] == "object" &&
1357                 !(arguments[1] instanceof Ci.nsIURI)) {
1358               let params = arguments[1];
1359               aReferrerURI          = params.referrerURI;
1360               aCharset              = params.charset;
1361               aPostData             = params.postData;
1362               aLoadInBackground     = params.inBackground;
1363               aAllowThirdPartyFixup = params.allowThirdPartyFixup;
1364               aFromExternal         = params.fromExternal;
1365               aRelatedToCurrent     = params.relatedToCurrent;
1366               aAllowMixedContent    = params.allowMixedContent;
1367               aSkipAnimation        = params.skipAnimation;
1368             }
1370             var bgLoad = (aLoadInBackground != null) ? aLoadInBackground :
1371                          Services.prefs.getBoolPref("browser.tabs.loadInBackground");
1372             var owner = bgLoad ? null : this.selectedTab;
1373             var tab = this.addTab(aURI, {
1374                                   referrerURI: aReferrerURI,
1375                                   charset: aCharset,
1376                                   postData: aPostData,
1377                                   ownerTab: owner,
1378                                   allowThirdPartyFixup: aAllowThirdPartyFixup,
1379                                   fromExternal: aFromExternal,
1380                                   relatedToCurrent: aRelatedToCurrent,
1381                                   skipAnimation: aSkipAnimation,
1382                                   allowMixedContent: aAllowMixedContent });
1383             if (!bgLoad)
1384               this.selectedTab = tab;
1386             return tab;
1387          ]]>
1388         </body>
1389       </method>
1391       <method name="loadTabs">
1392         <parameter name="aURIs"/>
1393         <parameter name="aLoadInBackground"/>
1394         <parameter name="aReplace"/>
1395         <body><![CDATA[
1396           if (!aURIs.length)
1397             return;
1399           // The tab selected after this new tab is closed (i.e. the new tab's
1400           // "owner") is the next adjacent tab (i.e. not the previously viewed tab)
1401           // when several urls are opened here (i.e. closing the first should select
1402           // the next of many URLs opened) or if the pref to have UI links opened in
1403           // the background is set (i.e. the link is not being opened modally)
1404           //
1405           // i.e.
1406           //    Number of URLs    Load UI Links in BG       Focus Last Viewed?
1407           //    == 1              false                     YES
1408           //    == 1              true                      NO
1409           //    > 1               false/true                NO
1410           var multiple = aURIs.length > 1;
1411           var owner = multiple || aLoadInBackground ? null : this.selectedTab;
1412           var firstTabAdded = null;
1414           if (aReplace) {
1415             try {
1416               this.loadURI(aURIs[0], null, null);
1417             } catch (e) {
1418               // Ignore failure in case a URI is wrong, so we can continue
1419               // opening the next ones.
1420             }
1421           }
1422           else
1423             firstTabAdded = this.addTab(aURIs[0], {ownerTab: owner, skipAnimation: multiple});
1425           var tabNum = this.tabContainer.selectedIndex;
1426           for (let i = 1; i < aURIs.length; ++i) {
1427             let tab = this.addTab(aURIs[i], {skipAnimation: true});
1428             if (aReplace)
1429               this.moveTabTo(tab, ++tabNum);
1430           }
1432           if (!aLoadInBackground) {
1433             if (firstTabAdded) {
1434               // .selectedTab setter focuses the content area
1435               this.selectedTab = firstTabAdded;
1436             }
1437             else
1438               this.selectedBrowser.focus();
1439           }
1440         ]]></body>
1441       </method>
1443       <method name="updateBrowserRemoteness">
1444         <parameter name="aBrowser"/>
1445         <parameter name="aShouldBeRemote"/>
1446         <body>
1447           <![CDATA[
1448             let isRemote = aBrowser.getAttribute("remote") == "true";
1449             if (isRemote == aShouldBeRemote)
1450               return false;
1452             let wasActive = document.activeElement == aBrowser;
1454             // Unhook our progress listener.
1455             let tab = this._getTabForBrowser(aBrowser);
1456             let index = tab._tPos;
1457             let filter = this.mTabFilters[index];
1458             aBrowser.webProgress.removeProgressListener(filter);
1460             // Change the "remote" attribute.
1461             let parent = aBrowser.parentNode;
1462             parent.removeChild(aBrowser);
1463             aBrowser.setAttribute("remote", aShouldBeRemote ? "true" : "false");
1464             parent.appendChild(aBrowser);
1466             // Restore the progress listener.
1467             aBrowser.webProgress.addProgressListener(filter, Ci.nsIWebProgress.NOTIFY_ALL);
1469             if (aShouldBeRemote)
1470               tab.setAttribute("remote", "true");
1471             else
1472               tab.removeAttribute("remote");
1474             if (wasActive)
1475               aBrowser.focus();
1477             return true;
1478           ]]>
1479         </body>
1480       </method>
1482 #ifdef MAKE_E10S_WORK
1483       <method name="updateBrowserRemotenessByURL">
1484         <parameter name="aBrowser"/>
1485         <parameter name="aURL"/>
1486         <body>
1487           <![CDATA[
1488             let shouldBeRemote = this._shouldBrowserBeRemote(aURL);
1489             return this.updateBrowserRemoteness(aBrowser, shouldBeRemote);
1490           ]]>
1491         </body>
1492       </method>
1494       <!--
1495         Returns true if we want to load the content for this URL in a
1496         remote process. Eventually this should just check whether aURL
1497         is unprivileged. Right now, though, we would like to load
1498         some unprivileged URLs (like about:neterror) in the main
1499         process since they interact with chrome code through
1500         BrowserOnClick.
1501       -->
1502       <method name="_shouldBrowserBeRemote">
1503         <parameter name="aURL"/>
1504         <body>
1505           <![CDATA[
1506             if (!gMultiProcessBrowser)
1507               return false;
1509             // loadURI in browser.xml treats null as about:blank
1510             if (!aURL)
1511               aURL = "about:blank";
1513             if (aURL.startsWith("about:") &&
1514                 aURL.toLowerCase() != "about:home" &&
1515                 aURL.toLowerCase() != "about:blank") {
1516               return false;
1517             }
1519             if (aURL.startsWith("chrome:"))
1520               return false;
1522             return true;
1523           ]]>
1524         </body>
1525       </method>
1526 #endif
1528       <method name="addTab">
1529         <parameter name="aURI"/>
1530         <parameter name="aReferrerURI"/>
1531         <parameter name="aCharset"/>
1532         <parameter name="aPostData"/>
1533         <parameter name="aOwner"/>
1534         <parameter name="aAllowThirdPartyFixup"/>
1535         <body>
1536           <![CDATA[
1537             const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
1538             var aFromExternal;
1539             var aRelatedToCurrent;
1540             var aSkipAnimation;
1541             var aAllowMixedContent;
1542             if (arguments.length == 2 &&
1543                 typeof arguments[1] == "object" &&
1544                 !(arguments[1] instanceof Ci.nsIURI)) {
1545               let params = arguments[1];
1546               aReferrerURI          = params.referrerURI;
1547               aCharset              = params.charset;
1548               aPostData             = params.postData;
1549               aOwner                = params.ownerTab;
1550               aAllowThirdPartyFixup = params.allowThirdPartyFixup;
1551               aFromExternal         = params.fromExternal;
1552               aRelatedToCurrent     = params.relatedToCurrent;
1553               aSkipAnimation        = params.skipAnimation;
1554               aAllowMixedContent    = params.allowMixedContent;
1555             }
1557             // if we're adding tabs, we're past interrupt mode, ditch the owner
1558             if (this.mCurrentTab.owner)
1559               this.mCurrentTab.owner = null;
1561             var t = document.createElementNS(NS_XUL, "tab");
1563             var uriIsAboutBlank = !aURI || aURI == "about:blank";
1565             t.setAttribute("crop", "end");
1566             t.setAttribute("onerror", "this.removeAttribute('image');");
1567             t.className = "tabbrowser-tab";
1569 #ifdef MAKE_E10S_WORK
1570             let remote = this._shouldBrowserBeRemote(aURI);
1571 #else
1572             let remote = gMultiProcessBrowser;
1573 #endif
1574             if (remote)
1575               t.setAttribute("remote", "true");
1577             this.tabContainer._unlockTabSizing();
1579             // When overflowing, new tabs are scrolled into view smoothly, which
1580             // doesn't go well together with the width transition. So we skip the
1581             // transition in that case.
1582             let animate = !aSkipAnimation &&
1583                           this.tabContainer.getAttribute("overflow") != "true" &&
1584                           Services.prefs.getBoolPref("browser.tabs.animate");
1585             if (!animate) {
1586               t.setAttribute("fadein", "true");
1587               setTimeout(function (tabContainer) {
1588                 tabContainer._handleNewTab(t);
1589               }, 0, this.tabContainer);
1590             }
1592             // invalidate caches
1593             this._browsers = null;
1594             this._visibleTabs = null;
1596             this.tabContainer.appendChild(t);
1598             // If this new tab is owned by another, assert that relationship
1599             if (aOwner)
1600               t.owner = aOwner;
1602             var b = document.createElementNS(
1603               "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
1604                                              "browser");
1605             b.setAttribute("type", "content-targetable");
1606             b.setAttribute("message", "true");
1607             b.setAttribute("messagemanagergroup", "browsers");
1608             b.setAttribute("contextmenu", this.getAttribute("contentcontextmenu"));
1609             b.setAttribute("tooltip", this.getAttribute("contenttooltip"));
1611             if (remote)
1612               b.setAttribute("remote", "true");
1614             if (window.gShowPageResizers && window.windowState == window.STATE_NORMAL) {
1615               b.setAttribute("showresizer", "true");
1616             }
1618             if (this.hasAttribute("autocompletepopup"))
1619               b.setAttribute("autocompletepopup", this.getAttribute("autocompletepopup"));
1621             if (this.hasAttribute("selectpopup"))
1622               b.setAttribute("selectpopup", this.getAttribute("selectpopup"));
1624             b.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
1626             // Create the browserStack container
1627             var stack = document.createElementNS(NS_XUL, "stack");
1628             stack.className = "browserStack";
1629             stack.appendChild(b);
1630             stack.setAttribute("flex", "1");
1632             // Create the browserContainer
1633             var browserContainer = document.createElementNS(NS_XUL, "vbox");
1634             browserContainer.className = "browserContainer";
1635             browserContainer.appendChild(stack);
1636             browserContainer.setAttribute("flex", "1");
1638             // Create the sidebar container
1639             var browserSidebarContainer = document.createElementNS(NS_XUL,
1640                                                                    "hbox");
1641             browserSidebarContainer.className = "browserSidebarContainer";
1642             browserSidebarContainer.appendChild(browserContainer);
1643             browserSidebarContainer.setAttribute("flex", "1");
1645             // Add the Message and the Browser to the box
1646             var notificationbox = document.createElementNS(NS_XUL,
1647                                                            "notificationbox");
1648             notificationbox.setAttribute("flex", "1");
1649             notificationbox.appendChild(browserSidebarContainer);
1651             var position = this.tabs.length - 1;
1652             var uniqueId = this._generateUniquePanelID();
1653             notificationbox.id = uniqueId;
1654             t.linkedPanel = uniqueId;
1655             t.linkedBrowser = b;
1656             t._tPos = position;
1657             this.tabContainer._setPositionalAttributes();
1659             // Prevent the superfluous initial load of a blank document
1660             // if we're going to load something other than about:blank.
1661             if (!uriIsAboutBlank) {
1662               b.setAttribute("nodefaultsrc", "true");
1663             }
1665             // NB: this appendChild call causes us to run constructors for the
1666             // browser element, which fires off a bunch of notifications. Some
1667             // of those notifications can cause code to run that inspects our
1668             // state, so it is important that the tab element is fully
1669             // initialized by this point.
1670             this.mPanelContainer.appendChild(notificationbox);
1672             // We've waited until the tab is in the DOM to set the label. This
1673             // allows the TabLabelModified event to be properly dispatched.
1674             if (!aURI || isBlankPageURL(aURI)) {
1675               t.label = this.mStringBundle.getString("tabs.emptyTabTitle");
1676             }
1678             this.tabContainer.updateVisibility();
1680             // wire up a progress listener for the new browser object.
1681             var tabListener = this.mTabProgressListener(t, b, uriIsAboutBlank);
1682             const filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
1683                                      .createInstance(Components.interfaces.nsIWebProgress);
1684             filter.addProgressListener(tabListener, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
1685             b.webProgress.addProgressListener(filter, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
1686             this.mTabListeners[position] = tabListener;
1687             this.mTabFilters[position] = filter;
1689             b.droppedLinkHandler = handleDroppedLink;
1691             // If we just created a new tab that loads the default
1692             // newtab url, swap in a preloaded page if possible.
1693             // Do nothing if we're a private window.
1694             let docShellsSwapped = false;
1695             if (aURI == BROWSER_NEW_TAB_URL &&
1696                 !PrivateBrowsingUtils.isWindowPrivate(window) &&
1697                 !gMultiProcessBrowser) {
1698               docShellsSwapped = gBrowserNewTabPreloader.newTab(t);
1699             } else if (aURI == "about:customizing") {
1700               docShellsSwapped = gCustomizationTabPreloader.newTab(t);
1701             }
1703             // Dispatch a new tab notification.  We do this once we're
1704             // entirely done, so that things are in a consistent state
1705             // even if the event listener opens or closes tabs.
1706             var evt = document.createEvent("Events");
1707             evt.initEvent("TabOpen", true, false);
1708             t.dispatchEvent(evt);
1710             // If we didn't swap docShells with a preloaded browser
1711             // then let's just continue loading the page normally.
1712             if (!docShellsSwapped && !uriIsAboutBlank) {
1713               // pretend the user typed this so it'll be available till
1714               // the document successfully loads
1715               if (aURI && gInitialPages.indexOf(aURI) == -1)
1716                 b.userTypedValue = aURI;
1718               let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
1719               if (aAllowThirdPartyFixup) {
1720                 flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
1721                 flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
1722               }
1723               if (aFromExternal)
1724                 flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL;
1725               if (aAllowMixedContent)
1726                 flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_MIXED_CONTENT;
1727               try {
1728                 b.loadURIWithFlags(aURI, flags, aReferrerURI, aCharset, aPostData);
1729               } catch (ex) {
1730                 Cu.reportError(ex);
1731               }
1732             }
1734             // We start our browsers out as inactive, and then maintain
1735             // activeness in the tab switcher.
1736             b.docShellIsActive = false;
1738             // Check if we're opening a tab related to the current tab and
1739             // move it to after the current tab.
1740             // aReferrerURI is null or undefined if the tab is opened from
1741             // an external application or bookmark, i.e. somewhere other
1742             // than the current tab.
1743             if ((aRelatedToCurrent == null ? aReferrerURI : aRelatedToCurrent) &&
1744                 Services.prefs.getBoolPref("browser.tabs.insertRelatedAfterCurrent")) {
1745               let newTabPos = (this._lastRelatedTab ||
1746                                this.selectedTab)._tPos + 1;
1747               if (this._lastRelatedTab)
1748                 this._lastRelatedTab.owner = null;
1749               else
1750                 t.owner = this.selectedTab;
1751               this.moveTabTo(t, newTabPos);
1752               this._lastRelatedTab = t;
1753             }
1755             if (animate) {
1756               mozRequestAnimationFrame(function () {
1757                 this.tabContainer._handleTabTelemetryStart(t, aURI);
1759                 // kick the animation off
1760                 t.setAttribute("fadein", "true");
1761               }.bind(this));
1762             }
1764             return t;
1765           ]]>
1766         </body>
1767       </method>
1769       <method name="warnAboutClosingTabs">
1770       <parameter name="aCloseTabs"/>
1771       <parameter name="aTab"/>
1772       <body>
1773         <![CDATA[
1774           var tabsToClose;
1775           switch (aCloseTabs) {
1776             case this.closingTabsEnum.ALL:
1777               tabsToClose = this.tabs.length - this._removingTabs.length -
1778                             gBrowser._numPinnedTabs;
1779               break;
1780             case this.closingTabsEnum.OTHER:
1781               tabsToClose = this.visibleTabs.length - 1 - gBrowser._numPinnedTabs;
1782               break;
1783             case this.closingTabsEnum.TO_END:
1784               if (!aTab)
1785                 throw new Error("Required argument missing: aTab");
1787               tabsToClose = this.getTabsToTheEndFrom(aTab).length;
1788               break;
1789             default:
1790               throw new Error("Invalid argument: " + aCloseTabs);
1791           }
1793           if (tabsToClose <= 1)
1794             return true;
1796           const pref = aCloseTabs == this.closingTabsEnum.ALL ?
1797                        "browser.tabs.warnOnClose" : "browser.tabs.warnOnCloseOtherTabs";
1798           var shouldPrompt = Services.prefs.getBoolPref(pref);
1799           if (!shouldPrompt)
1800             return true;
1802           var ps = Services.prompt;
1804           // default to true: if it were false, we wouldn't get this far
1805           var warnOnClose = { value: true };
1806           var bundle = this.mStringBundle;
1808           // focus the window before prompting.
1809           // this will raise any minimized window, which will
1810           // make it obvious which window the prompt is for and will
1811           // solve the problem of windows "obscuring" the prompt.
1812           // see bug #350299 for more details
1813           window.focus();
1814           var warningMessage =
1815             PluralForm.get(tabsToClose, bundle.getString("tabs.closeWarningMultiple"))
1816                       .replace("#1", tabsToClose);
1817           var buttonPressed =
1818             ps.confirmEx(window,
1819                          bundle.getString("tabs.closeWarningTitle"),
1820                          warningMessage,
1821                          (ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_0)
1822                          + (ps.BUTTON_TITLE_CANCEL * ps.BUTTON_POS_1),
1823                          bundle.getString("tabs.closeButtonMultiple"),
1824                          null, null,
1825                          aCloseTabs == this.closingTabsEnum.ALL ?
1826                            bundle.getString("tabs.closeWarningPromptMe") : null,
1827                          warnOnClose);
1828           var reallyClose = (buttonPressed == 0);
1830           // don't set the pref unless they press OK and it's false
1831           if (aCloseTabs == this.closingTabsEnum.ALL && reallyClose && !warnOnClose.value)
1832             Services.prefs.setBoolPref(pref, false);
1834           return reallyClose;
1835         ]]>
1836       </body>
1837       </method>
1839       <method name="getTabsToTheEndFrom">
1840         <parameter name="aTab"/>
1841         <body>
1842           <![CDATA[
1843             var tabsToEnd = [];
1844             let tabs = this.visibleTabs;
1845             for (let i = tabs.length - 1; tabs[i] != aTab && i >= 0; --i) {
1846               tabsToEnd.push(tabs[i]);
1847             }
1848             return tabsToEnd.reverse();
1849           ]]>
1850         </body>
1851       </method>
1853       <method name="removeTabsToTheEndFrom">
1854         <parameter name="aTab"/>
1855         <body>
1856           <![CDATA[
1857             if (this.warnAboutClosingTabs(this.closingTabsEnum.TO_END, aTab)) {
1858               let tabs = this.getTabsToTheEndFrom(aTab);
1859               for (let i = tabs.length - 1; i >= 0; --i) {
1860                 this.removeTab(tabs[i], {animate: true});
1861               }
1862             }
1863           ]]>
1864         </body>
1865       </method>
1867       <method name="removeAllTabsBut">
1868         <parameter name="aTab"/>
1869         <body>
1870           <![CDATA[
1871             if (aTab.pinned)
1872               return;
1874             if (this.warnAboutClosingTabs(this.closingTabsEnum.OTHER)) {
1875               let tabs = this.visibleTabs;
1876               this.selectedTab = aTab;
1878               for (let i = tabs.length - 1; i >= 0; --i) {
1879                 if (tabs[i] != aTab && !tabs[i].pinned)
1880                   this.removeTab(tabs[i], {animate: true});
1881               }
1882             }
1883           ]]>
1884         </body>
1885       </method>
1887       <method name="removeCurrentTab">
1888         <parameter name="aParams"/>
1889         <body>
1890           <![CDATA[
1891             this.removeTab(this.mCurrentTab, aParams);
1892           ]]>
1893         </body>
1894       </method>
1896       <field name="_removingTabs">
1897         []
1898       </field>
1900       <method name="removeTab">
1901         <parameter name="aTab"/>
1902         <parameter name="aParams"/>
1903         <body>
1904           <![CDATA[
1905             if (aParams) {
1906               var animate = aParams.animate;
1907               var byMouse = aParams.byMouse;
1908             }
1910             // Handle requests for synchronously removing an already
1911             // asynchronously closing tab.
1912             if (!animate &&
1913                 aTab.closing) {
1914               this._endRemoveTab(aTab);
1915               return;
1916             }
1918             var isLastTab = (this.tabs.length - this._removingTabs.length == 1);
1920             if (!this._beginRemoveTab(aTab, false, null, true))
1921               return;
1923             if (!aTab.pinned && !aTab.hidden && aTab._fullyOpen && byMouse)
1924               this.tabContainer._lockTabSizing(aTab);
1925             else
1926               this.tabContainer._unlockTabSizing();
1928             if (!animate /* the caller didn't opt in */ ||
1929                 isLastTab ||
1930                 aTab.pinned ||
1931                 aTab.hidden ||
1932                 this._removingTabs.length > 3 /* don't want lots of concurrent animations */ ||
1933                 aTab.getAttribute("fadein") != "true" /* fade-in transition hasn't been triggered yet */ ||
1934                 window.getComputedStyle(aTab).maxWidth == "0.1px" /* fade-in transition hasn't moved yet */ ||
1935                 !Services.prefs.getBoolPref("browser.tabs.animate")) {
1936               this._endRemoveTab(aTab);
1937               return;
1938             }
1940             this.tabContainer._handleTabTelemetryStart(aTab);
1942             this._blurTab(aTab);
1943             aTab.style.maxWidth = ""; // ensure that fade-out transition happens
1944             aTab.removeAttribute("fadein");
1946             setTimeout(function (tab, tabbrowser) {
1947               if (tab.parentNode &&
1948                   window.getComputedStyle(tab).maxWidth == "0.1px") {
1949                 NS_ASSERT(false, "Giving up waiting for the tab closing animation to finish (bug 608589)");
1950                 tabbrowser._endRemoveTab(tab);
1951               }
1952             }, 3000, aTab, this);
1953           ]]>
1954         </body>
1955       </method>
1957       <!-- Tab close requests are ignored if the window is closing anyway,
1958            e.g. when holding Ctrl+W. -->
1959       <field name="_windowIsClosing">
1960         false
1961       </field>
1963       <method name="_beginRemoveTab">
1964         <parameter name="aTab"/>
1965         <parameter name="aTabWillBeMoved"/>
1966         <parameter name="aCloseWindowWithLastTab"/>
1967         <parameter name="aCloseWindowFastpath"/>
1968         <body>
1969           <![CDATA[
1970             if (aTab.closing ||
1971                 this._windowIsClosing)
1972               return false;
1974             var browser = this.getBrowserForTab(aTab);
1975             if (!aTab._pendingPermitUnload && !aTabWillBeMoved) {
1976               let ds = browser.docShell;
1977               if (ds && ds.contentViewer) {
1978                 // We need to block while calling permitUnload() because it
1979                 // processes the event queue and may lead to another removeTab()
1980                 // call before permitUnload() even returned.
1981                 aTab._pendingPermitUnload = true;
1982                 let permitUnload = ds.contentViewer.permitUnload();
1983                 delete aTab._pendingPermitUnload;
1984                 // If we were closed during onbeforeunload, we return false now
1985                 // so we don't (try to) close the same tab again. Of course, we
1986                 // also stop if the unload was cancelled by the user:
1987                 if (aTab.closing || !permitUnload) {
1988                   // NB: deliberately keep the _closedDuringPermitUnload set to
1989                   // true so we keep exiting early in case of multiple calls.
1990                   return false;
1991                 }
1992               }
1993             }
1995             var closeWindow = false;
1996             var newTab = false;
1997             if (this.tabs.length - this._removingTabs.length == 1) {
1998               closeWindow = aCloseWindowWithLastTab != null ? aCloseWindowWithLastTab :
1999                             !window.toolbar.visible ||
2000                               Services.prefs.getBoolPref("browser.tabs.closeWindowWithLastTab");
2002               // Closing the tab and replacing it with a blank one is notably slower
2003               // than closing the window right away. If the caller opts in, take
2004               // the fast path.
2005               if (closeWindow &&
2006                   aCloseWindowFastpath &&
2007                   this._removingTabs.length == 0) {
2008                 // This call actually closes the window, unless the user
2009                 // cancels the operation.  We are finished here in both cases.
2010                 this._windowIsClosing = window.closeWindow(true, window.warnAboutClosingWindow);
2011                 return null;
2012               }
2014               newTab = true;
2015             }
2017             aTab.closing = true;
2018             this._removingTabs.push(aTab);
2019             this._visibleTabs = null; // invalidate cache
2021             // Invalidate hovered tab state tracking for this closing tab.
2022             if (this.tabContainer._hoveredTab == aTab)
2023               aTab._mouseleave();
2025             if (newTab)
2026               this.addTab(BROWSER_NEW_TAB_URL, {skipAnimation: true});
2027             else
2028               this.tabContainer.updateVisibility();
2030             // We're committed to closing the tab now.
2031             // Dispatch a notification.
2032             // We dispatch it before any teardown so that event listeners can
2033             // inspect the tab that's about to close.
2034             var evt = document.createEvent("UIEvent");
2035             evt.initUIEvent("TabClose", true, false, window, aTabWillBeMoved ? 1 : 0);
2036             aTab.dispatchEvent(evt);
2038             if (!aTabWillBeMoved && !gMultiProcessBrowser) {
2039               // Prevent this tab from showing further dialogs, since we're closing it
2040               var windowUtils = browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).
2041                                 getInterface(Ci.nsIDOMWindowUtils);
2042               windowUtils.disableDialogs();
2043             }
2045             // Remove the tab's filter and progress listener.
2046             const filter = this.mTabFilters[aTab._tPos];
2048             browser.webProgress.removeProgressListener(filter);
2050             filter.removeProgressListener(this.mTabListeners[aTab._tPos]);
2051             this.mTabListeners[aTab._tPos].destroy();
2053             if (browser.registeredOpenURI && !aTabWillBeMoved) {
2054               this._placesAutocomplete.unregisterOpenPage(browser.registeredOpenURI);
2055               this._unifiedComplete.unregisterOpenPage(browser.registeredOpenURI);
2056               delete browser.registeredOpenURI;
2057             }
2059             // We are no longer the primary content area.
2060             browser.setAttribute("type", "content-targetable");
2062             // Remove this tab as the owner of any other tabs, since it's going away.
2063             Array.forEach(this.tabs, function (tab) {
2064               if ("owner" in tab && tab.owner == aTab)
2065                 // |tab| is a child of the tab we're removing, make it an orphan
2066                 tab.owner = null;
2067             });
2069             aTab._endRemoveArgs = [closeWindow, newTab];
2070             return true;
2071           ]]>
2072         </body>
2073       </method>
2075       <method name="_endRemoveTab">
2076         <parameter name="aTab"/>
2077         <body>
2078           <![CDATA[
2079             if (!aTab || !aTab._endRemoveArgs)
2080               return;
2082             var [aCloseWindow, aNewTab] = aTab._endRemoveArgs;
2083             aTab._endRemoveArgs = null;
2085             if (this._windowIsClosing) {
2086               aCloseWindow = false;
2087               aNewTab = false;
2088             }
2090             this._lastRelatedTab = null;
2092             // update the UI early for responsiveness
2093             aTab.collapsed = true;
2094             this.tabContainer._fillTrailingGap();
2095             this._blurTab(aTab);
2097             this._removingTabs.splice(this._removingTabs.indexOf(aTab), 1);
2099             if (aCloseWindow) {
2100               this._windowIsClosing = true;
2101               while (this._removingTabs.length)
2102                 this._endRemoveTab(this._removingTabs[0]);
2103             } else if (!this._windowIsClosing) {
2104               if (aNewTab)
2105                 focusAndSelectUrlBar();
2107               // workaround for bug 345399
2108               this.tabContainer.mTabstrip._updateScrollButtonsDisabledState();
2109             }
2111             // We're going to remove the tab and the browser now.
2112             // Clean up mTabFilters and mTabListeners now rather than in
2113             // _beginRemoveTab, so that their size is always in sync with the
2114             // number of tabs and browsers (the xbl destructor depends on this).
2115             this.mTabFilters.splice(aTab._tPos, 1);
2116             this.mTabListeners.splice(aTab._tPos, 1);
2118             var browser = this.getBrowserForTab(aTab);
2120             // Because of the way XBL works (fields just set JS
2121             // properties on the element) and the code we have in place
2122             // to preserve the JS objects for any elements that have
2123             // JS properties set on them, the browser element won't be
2124             // destroyed until the document goes away.  So we force a
2125             // cleanup ourselves.
2126             // This has to happen before we remove the child so that the
2127             // XBL implementation of nsIObserver still works.
2128             browser.destroy();
2130             var wasPinned = aTab.pinned;
2132             // Invalidate browsers cache, as the tab is removed from the
2133             // tab container.
2134             this._browsers = null;
2136             // Remove the tab ...
2137             this.tabContainer.removeChild(aTab);
2139             // ... and fix up the _tPos properties immediately.
2140             for (let i = aTab._tPos; i < this.tabs.length; i++)
2141               this.tabs[i]._tPos = i;
2143             if (!this._windowIsClosing) {
2144               if (wasPinned)
2145                 this.tabContainer._positionPinnedTabs();
2147               // update tab close buttons state
2148               this.tabContainer.adjustTabstrip();
2150               setTimeout(function(tabs) {
2151                 tabs._lastTabClosedByMouse = false;
2152               }, 0, this.tabContainer);
2153             }
2155             // update tab positional properties and attributes
2156             this.selectedTab._selected = true;
2157             this.tabContainer._setPositionalAttributes();
2159             // Removing the panel requires fixing up selectedPanel immediately
2160             // (see below), which would be hindered by the potentially expensive
2161             // browser removal. So we remove the browser and the panel in two
2162             // steps.
2164             var panel = this.getNotificationBox(browser);
2166             // This will unload the document. An unload handler could remove
2167             // dependant tabs, so it's important that the tabbrowser is now in
2168             // a consistent state (tab removed, tab positions updated, etc.).
2169             browser.parentNode.removeChild(browser);
2171             // Release the browser in case something is erroneously holding a
2172             // reference to the tab after its removal.
2173             aTab.linkedBrowser = null;
2175             // As the browser is removed, the removal of a dependent document can
2176             // cause the whole window to close. So at this point, it's possible
2177             // that the binding is destructed.
2178             if (this.mTabBox) {
2179               this.mPanelContainer.removeChild(panel);
2180             }
2182             if (aCloseWindow)
2183               this._windowIsClosing = closeWindow(true, window.warnAboutClosingWindow);
2184           ]]>
2185         </body>
2186       </method>
2188       <method name="_blurTab">
2189         <parameter name="aTab"/>
2190         <body>
2191           <![CDATA[
2192             if (!aTab.selected)
2193               return;
2195             if (aTab.owner &&
2196                 !aTab.owner.hidden &&
2197                 !aTab.owner.closing &&
2198                 Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose")) {
2199               this.selectedTab = aTab.owner;
2200               return;
2201             }
2203             // Switch to a visible tab unless there aren't any others remaining
2204             let remainingTabs = this.visibleTabs;
2205             let numTabs = remainingTabs.length;
2206             if (numTabs == 0 || numTabs == 1 && remainingTabs[0] == aTab) {
2207               remainingTabs = Array.filter(this.tabs, function(tab) {
2208                 return !tab.closing;
2209               }, this);
2210             }
2212             // Try to find a remaining tab that comes after the given tab
2213             var tab = aTab;
2214             do {
2215               tab = tab.nextSibling;
2216             } while (tab && remainingTabs.indexOf(tab) == -1);
2218             if (!tab) {
2219               tab = aTab;
2221               do {
2222                 tab = tab.previousSibling;
2223               } while (tab && remainingTabs.indexOf(tab) == -1);
2224             }
2226             this.selectedTab = tab;
2227           ]]>
2228         </body>
2229       </method>
2231       <method name="swapNewTabWithBrowser">
2232         <parameter name="aNewTab"/>
2233         <parameter name="aBrowser"/>
2234         <body>
2235           <![CDATA[
2236             // The browser must be standalone.
2237             if (aBrowser.getTabBrowser())
2238               throw Cr.NS_ERROR_INVALID_ARG;
2240             // The tab is definitely not loading.
2241             aNewTab.removeAttribute("busy");
2242             if (aNewTab.selected) {
2243               this.mIsBusy = false;
2244             }
2246             this._swapBrowserDocShells(aNewTab, aBrowser);
2248             // Update the new tab's title.
2249             this.setTabTitle(aNewTab);
2251             if (aNewTab.selected) {
2252               this.updateCurrentBrowser(true);
2253             }
2254           ]]>
2255         </body>
2256       </method>
2258       <method name="swapBrowsersAndCloseOther">
2259         <parameter name="aOurTab"/>
2260         <parameter name="aOtherTab"/>
2261         <body>
2262           <![CDATA[
2263             // Do not allow transfering a private tab to a non-private window
2264             // and vice versa.
2265             if (PrivateBrowsingUtils.isWindowPrivate(window) !=
2266                 PrivateBrowsingUtils.isWindowPrivate(aOtherTab.ownerDocument.defaultView))
2267               return;
2269             // That's gBrowser for the other window, not the tab's browser!
2270             var remoteBrowser = aOtherTab.ownerDocument.defaultView.gBrowser;
2271             var isPending = aOtherTab.hasAttribute("pending");
2273             // First, start teardown of the other browser.  Make sure to not
2274             // fire the beforeunload event in the process.  Close the other
2275             // window if this was its last tab.
2276             if (!remoteBrowser._beginRemoveTab(aOtherTab, true, true))
2277               return;
2279             let ourBrowser = this.getBrowserForTab(aOurTab);
2280             let otherBrowser = aOtherTab.linkedBrowser;
2282             // If the other tab is pending (i.e. has not been restored, yet)
2283             // then do not switch docShells but retrieve the other tab's state
2284             // and apply it to our tab.
2285             if (isPending) {
2286               SessionStore.setTabState(aOurTab, SessionStore.getTabState(aOtherTab));
2288               // Make sure to unregister any open URIs.
2289               this._swapRegisteredOpenURIs(ourBrowser, otherBrowser);
2290             } else {
2291               // Workarounds for bug 458697
2292               // Icon might have been set on DOMLinkAdded, don't override that.
2293               if (!ourBrowser.mIconURL && otherBrowser.mIconURL)
2294                 this.setIcon(aOurTab, otherBrowser.mIconURL);
2295               var isBusy = aOtherTab.hasAttribute("busy");
2296               if (isBusy) {
2297                 aOurTab.setAttribute("busy", "true");
2298                 this._tabAttrModified(aOurTab);
2299                 if (aOurTab.selected)
2300                   this.mIsBusy = true;
2301               }
2303               this._swapBrowserDocShells(aOurTab, otherBrowser);
2304             }
2306             // Handle findbar data (if any)
2307             let otherFindBar = aOtherTab._findBar;
2308             if (otherFindBar &&
2309                 otherFindBar.findMode == otherFindBar.FIND_NORMAL) {
2310               let ourFindBar = this.getFindBar(aOurTab);
2311               ourFindBar._findField.value = otherFindBar._findField.value;
2312               if (!otherFindBar.hidden)
2313                 ourFindBar.onFindCommand();
2314             }
2316             // Finish tearing down the tab that's going away.
2317             remoteBrowser._endRemoveTab(aOtherTab);
2319             if (isBusy)
2320               this.setTabTitleLoading(aOurTab);
2321             else
2322               this.setTabTitle(aOurTab);
2324             // If the tab was already selected (this happpens in the scenario
2325             // of replaceTabWithWindow), notify onLocationChange, etc.
2326             if (aOurTab.selected)
2327               this.updateCurrentBrowser(true);
2328           ]]>
2329         </body>
2330       </method>
2332       <method name="_swapBrowserDocShells">
2333         <parameter name="aOurTab"/>
2334         <parameter name="aOtherBrowser"/>
2335         <body>
2336           <![CDATA[
2337             // Unhook our progress listener
2338             let index = aOurTab._tPos;
2339             const filter = this.mTabFilters[index];
2340             let tabListener = this.mTabListeners[index];
2341             let ourBrowser = this.getBrowserForTab(aOurTab);
2342             ourBrowser.webProgress.removeProgressListener(filter);
2343             filter.removeProgressListener(tabListener);
2345             // Make sure to unregister any open URIs.
2346             this._swapRegisteredOpenURIs(ourBrowser, aOtherBrowser);
2348             // Give others a chance to swap state.
2349             let event = new CustomEvent("SwapDocShells", {"detail": aOtherBrowser});
2350             ourBrowser.dispatchEvent(event);
2351             event = new CustomEvent("SwapDocShells", {"detail": ourBrowser});
2352             aOtherBrowser.dispatchEvent(event);
2354             // Swap the docshells
2355             ourBrowser.swapDocShells(aOtherBrowser);
2357             // Restore the progress listener
2358             this.mTabListeners[index] = tabListener =
2359               this.mTabProgressListener(aOurTab, ourBrowser, false);
2361             const notifyAll = Ci.nsIWebProgress.NOTIFY_ALL;
2362             filter.addProgressListener(tabListener, notifyAll);
2363             ourBrowser.webProgress.addProgressListener(filter, notifyAll);
2364           ]]>
2365         </body>
2366       </method>
2368       <method name="_swapRegisteredOpenURIs">
2369         <parameter name="aOurBrowser"/>
2370         <parameter name="aOtherBrowser"/>
2371         <body>
2372           <![CDATA[
2373             // If the current URI is registered as open remove it from the list.
2374             if (aOurBrowser.registeredOpenURI) {
2375               this._placesAutocomplete.unregisterOpenPage(aOurBrowser.registeredOpenURI);
2376               this._unifiedComplete.unregisterOpenPage(aOurBrowser.registeredOpenURI);
2377               delete aOurBrowser.registeredOpenURI;
2378             }
2380             // If the other/new URI is registered as open then copy it over.
2381             if (aOtherBrowser.registeredOpenURI) {
2382               aOurBrowser.registeredOpenURI = aOtherBrowser.registeredOpenURI;
2383               delete aOtherBrowser.registeredOpenURI;
2384             }
2385           ]]>
2386         </body>
2387       </method>
2389       <method name="reloadAllTabs">
2390         <body>
2391           <![CDATA[
2392             let tabs = this.visibleTabs;
2393             let l = tabs.length;
2394             for (var i = 0; i < l; i++) {
2395               try {
2396                 this.getBrowserForTab(tabs[i]).reload();
2397               } catch (e) {
2398                 // ignore failure to reload so others will be reloaded
2399               }
2400             }
2401           ]]>
2402         </body>
2403       </method>
2405       <method name="reloadTab">
2406         <parameter name="aTab"/>
2407         <body>
2408           <![CDATA[
2409             this.getBrowserForTab(aTab).reload();
2410           ]]>
2411         </body>
2412       </method>
2414       <method name="addProgressListener">
2415         <parameter name="aListener"/>
2416         <body>
2417           <![CDATA[
2418             if (arguments.length != 1) {
2419               Components.utils.reportError("gBrowser.addProgressListener was " +
2420                                            "called with a second argument, " +
2421                                            "which is not supported. See bug " +
2422                                            "608628. Call stack: " + new Error().stack);
2423             }
2425             this.mProgressListeners.push(aListener);
2426           ]]>
2427         </body>
2428       </method>
2430       <method name="removeProgressListener">
2431         <parameter name="aListener"/>
2432         <body>
2433           <![CDATA[
2434             this.mProgressListeners =
2435               this.mProgressListeners.filter(function (l) l != aListener);
2436          ]]>
2437         </body>
2438       </method>
2440       <method name="addTabsProgressListener">
2441         <parameter name="aListener"/>
2442         <body>
2443           this.mTabsProgressListeners.push(aListener);
2444         </body>
2445       </method>
2447       <method name="removeTabsProgressListener">
2448         <parameter name="aListener"/>
2449         <body>
2450         <![CDATA[
2451           this.mTabsProgressListeners =
2452             this.mTabsProgressListeners.filter(function (l) l != aListener);
2453         ]]>
2454         </body>
2455       </method>
2457       <method name="getBrowserForTab">
2458         <parameter name="aTab"/>
2459         <body>
2460         <![CDATA[
2461           return aTab.linkedBrowser;
2462         ]]>
2463         </body>
2464       </method>
2466       <method name="showOnlyTheseTabs">
2467         <parameter name="aTabs"/>
2468         <body>
2469         <![CDATA[
2470           Array.forEach(this.tabs, function(tab) {
2471             if (aTabs.indexOf(tab) == -1)
2472               this.hideTab(tab);
2473             else
2474               this.showTab(tab);
2475           }, this);
2477           this.tabContainer._handleTabSelect(false);
2478         ]]>
2479         </body>
2480       </method>
2482       <method name="showTab">
2483         <parameter name="aTab"/>
2484         <body>
2485         <![CDATA[
2486           if (aTab.hidden) {
2487             aTab.removeAttribute("hidden");
2488             this._visibleTabs = null; // invalidate cache
2490             this.tabContainer.adjustTabstrip();
2492             this.tabContainer._setPositionalAttributes();
2494             let event = document.createEvent("Events");
2495             event.initEvent("TabShow", true, false);
2496             aTab.dispatchEvent(event);
2497           }
2498         ]]>
2499         </body>
2500       </method>
2502       <method name="hideTab">
2503         <parameter name="aTab"/>
2504         <body>
2505         <![CDATA[
2506           if (!aTab.hidden && !aTab.pinned && !aTab.selected &&
2507               !aTab.closing) {
2508             aTab.setAttribute("hidden", "true");
2509             this._visibleTabs = null; // invalidate cache
2511             this.tabContainer.adjustTabstrip();
2513             this.tabContainer._setPositionalAttributes();
2515             let event = document.createEvent("Events");
2516             event.initEvent("TabHide", true, false);
2517             aTab.dispatchEvent(event);
2518           }
2519         ]]>
2520         </body>
2521       </method>
2523       <method name="selectTabAtIndex">
2524         <parameter name="aIndex"/>
2525         <parameter name="aEvent"/>
2526         <body>
2527         <![CDATA[
2528           let tabs = this.visibleTabs;
2530           // count backwards for aIndex < 0
2531           if (aIndex < 0)
2532             aIndex += tabs.length;
2534           if (aIndex >= 0 && aIndex < tabs.length)
2535             this.selectedTab = tabs[aIndex];
2537           if (aEvent) {
2538             aEvent.preventDefault();
2539             aEvent.stopPropagation();
2540           }
2541         ]]>
2542         </body>
2543       </method>
2545       <property name="selectedTab">
2546         <getter>
2547           return this.mCurrentTab;
2548         </getter>
2549         <setter>
2550           <![CDATA[
2551           // Update the tab
2552           this.mTabBox.selectedTab = val;
2553           return val;
2554           ]]>
2555         </setter>
2556       </property>
2558       <property name="selectedBrowser"
2559                 onget="return this.mCurrentBrowser;"
2560                 readonly="true"/>
2562       <property name="browsers" readonly="true">
2563        <getter>
2564           <![CDATA[
2565             return this._browsers ||
2566                    (this._browsers = Array.map(this.tabs, function (tab) tab.linkedBrowser));
2567           ]]>
2568         </getter>
2569       </property>
2570       <field name="_browsers">null</field>
2572       <!-- Moves a tab to a new browser window, unless it's already the only tab
2573            in the current window, in which case this will do nothing. -->
2574       <method name="replaceTabWithWindow">
2575         <parameter name="aTab"/>
2576         <parameter name="aOptions"/>
2577         <body>
2578           <![CDATA[
2579             if (this.tabs.length == 1)
2580               return null;
2582             let event = new CustomEvent("TabBecomingWindow", {
2583               bubbles: true,
2584               cancelable: true
2585             });
2586             aTab.dispatchEvent(event);
2587             if (event.defaultPrevented) {
2588               return null;
2589             }
2591             var options = "chrome,dialog=no,all";
2592             for (var name in aOptions)
2593               options += "," + name + "=" + aOptions[name];
2595             // tell a new window to take the "dropped" tab
2596             return window.openDialog(getBrowserURL(), "_blank", options, aTab);
2597           ]]>
2598         </body>
2599       </method>
2601 #ifdef E10S_TESTING_ONLY
2602       <!-- Opens a given tab to a non-remote window. -->
2603       <method name="openNonRemoteWindow">
2604         <parameter name="aTab"/>
2605         <body>
2606           <![CDATA[
2607             let url = aTab.linkedBrowser.currentURI.spec;
2608             return window.openDialog("chrome://browser/content/", "_blank", "chrome,all,dialog=no,non-remote", url);
2609           ]]>
2610         </body>
2611       </method>
2612 #endif
2614       <method name="moveTabTo">
2615         <parameter name="aTab"/>
2616         <parameter name="aIndex"/>
2617         <body>
2618         <![CDATA[
2619           var oldPosition = aTab._tPos;
2620           if (oldPosition == aIndex)
2621             return;
2623           // Don't allow mixing pinned and unpinned tabs.
2624           if (aTab.pinned)
2625             aIndex = Math.min(aIndex, this._numPinnedTabs - 1);
2626           else
2627             aIndex = Math.max(aIndex, this._numPinnedTabs);
2628           if (oldPosition == aIndex)
2629             return;
2631           this._lastRelatedTab = null;
2633           this.mTabFilters.splice(aIndex, 0, this.mTabFilters.splice(aTab._tPos, 1)[0]);
2634           this.mTabListeners.splice(aIndex, 0, this.mTabListeners.splice(aTab._tPos, 1)[0]);
2636           let wasFocused = (document.activeElement == this.mCurrentTab);
2638           aIndex = aIndex < aTab._tPos ? aIndex: aIndex+1;
2639           this.mCurrentTab._selected = false;
2641           // invalidate caches
2642           this._browsers = null;
2643           this._visibleTabs = null;
2645           // use .item() instead of [] because dragging to the end of the strip goes out of
2646           // bounds: .item() returns null (so it acts like appendChild), but [] throws
2647           this.tabContainer.insertBefore(aTab, this.tabs.item(aIndex));
2649           for (let i = 0; i < this.tabs.length; i++) {
2650             this.tabs[i]._tPos = i;
2651             this.tabs[i]._selected = false;
2652           }
2653           this.mCurrentTab._selected = true;
2655           if (wasFocused)
2656             this.mCurrentTab.focus();
2658           this.tabContainer._handleTabSelect(false);
2660           if (aTab.pinned)
2661             this.tabContainer._positionPinnedTabs();
2663           this.tabContainer._setPositionalAttributes();
2665           var evt = document.createEvent("UIEvents");
2666           evt.initUIEvent("TabMove", true, false, window, oldPosition);
2667           aTab.dispatchEvent(evt);
2668         ]]>
2669         </body>
2670       </method>
2672       <method name="moveTabForward">
2673         <body>
2674           <![CDATA[
2675             let nextTab = this.mCurrentTab.nextSibling;
2676             while (nextTab && nextTab.hidden)
2677               nextTab = nextTab.nextSibling;
2679             if (nextTab)
2680               this.moveTabTo(this.mCurrentTab, nextTab._tPos);
2681             else if (this.arrowKeysShouldWrap)
2682               this.moveTabToStart();
2683           ]]>
2684         </body>
2685       </method>
2687       <method name="moveTabBackward">
2688         <body>
2689           <![CDATA[
2690             let previousTab = this.mCurrentTab.previousSibling;
2691             while (previousTab && previousTab.hidden)
2692               previousTab = previousTab.previousSibling;
2694             if (previousTab)
2695               this.moveTabTo(this.mCurrentTab, previousTab._tPos);
2696             else if (this.arrowKeysShouldWrap)
2697               this.moveTabToEnd();
2698           ]]>
2699         </body>
2700       </method>
2702       <method name="moveTabToStart">
2703         <body>
2704           <![CDATA[
2705             var tabPos = this.mCurrentTab._tPos;
2706             if (tabPos > 0)
2707               this.moveTabTo(this.mCurrentTab, 0);
2708           ]]>
2709         </body>
2710       </method>
2712       <method name="moveTabToEnd">
2713         <body>
2714           <![CDATA[
2715             var tabPos = this.mCurrentTab._tPos;
2716             if (tabPos < this.browsers.length - 1)
2717               this.moveTabTo(this.mCurrentTab, this.browsers.length - 1);
2718           ]]>
2719         </body>
2720       </method>
2722       <method name="moveTabOver">
2723         <parameter name="aEvent"/>
2724         <body>
2725           <![CDATA[
2726             var direction = window.getComputedStyle(this.parentNode, null).direction;
2727             if ((direction == "ltr" && aEvent.keyCode == KeyEvent.DOM_VK_RIGHT) ||
2728                 (direction == "rtl" && aEvent.keyCode == KeyEvent.DOM_VK_LEFT))
2729               this.moveTabForward();
2730             else
2731               this.moveTabBackward();
2732           ]]>
2733         </body>
2734       </method>
2736       <method name="duplicateTab">
2737         <parameter name="aTab"/><!-- can be from a different window as well -->
2738         <body>
2739           <![CDATA[
2740             return SessionStore.duplicateTab(window, aTab);
2741           ]]>
2742         </body>
2743       </method>
2745       <!-- BEGIN FORWARDED BROWSER PROPERTIES.  IF YOU ADD A PROPERTY TO THE BROWSER ELEMENT
2746            MAKE SURE TO ADD IT HERE AS WELL. -->
2747       <property name="canGoBack"
2748                 onget="return this.mCurrentBrowser.canGoBack;"
2749                 readonly="true"/>
2751       <property name="canGoForward"
2752                 onget="return this.mCurrentBrowser.canGoForward;"
2753                 readonly="true"/>
2755       <method name="goBack">
2756         <body>
2757           <![CDATA[
2758             return this.mCurrentBrowser.goBack();
2759           ]]>
2760         </body>
2761       </method>
2763       <method name="goForward">
2764         <body>
2765           <![CDATA[
2766             return this.mCurrentBrowser.goForward();
2767           ]]>
2768         </body>
2769       </method>
2771       <method name="reload">
2772         <body>
2773           <![CDATA[
2774             return this.mCurrentBrowser.reload();
2775           ]]>
2776         </body>
2777       </method>
2779       <method name="reloadWithFlags">
2780         <parameter name="aFlags"/>
2781         <body>
2782           <![CDATA[
2783             return this.mCurrentBrowser.reloadWithFlags(aFlags);
2784           ]]>
2785         </body>
2786       </method>
2788       <method name="stop">
2789         <body>
2790           <![CDATA[
2791             return this.mCurrentBrowser.stop();
2792           ]]>
2793         </body>
2794       </method>
2796       <!-- throws exception for unknown schemes -->
2797       <method name="loadURI">
2798         <parameter name="aURI"/>
2799         <parameter name="aReferrerURI"/>
2800         <parameter name="aCharset"/>
2801         <body>
2802           <![CDATA[
2803 #ifdef MAKE_E10S_WORK
2804             this.updateBrowserRemotenessByURL(this.mCurrentBrowser, aURI);
2805             try {
2806 #endif
2807             return this.mCurrentBrowser.loadURI(aURI, aReferrerURI, aCharset);
2808 #ifdef MAKE_E10S_WORK
2809             } catch (e) {
2810               let url = this.mCurrentBrowser.currentURI.spec;
2811               this.updateBrowserRemotenessByURL(this.mCurrentBrowser, url);
2812               throw e;
2813             }
2814 #endif
2815           ]]>
2816         </body>
2817       </method>
2819       <!-- throws exception for unknown schemes -->
2820       <method name="loadURIWithFlags">
2821         <parameter name="aURI"/>
2822         <parameter name="aFlags"/>
2823         <parameter name="aReferrerURI"/>
2824         <parameter name="aCharset"/>
2825         <parameter name="aPostData"/>
2826         <body>
2827           <![CDATA[
2828 #ifdef MAKE_E10S_WORK
2829             this.updateBrowserRemotenessByURL(this.mCurrentBrowser, aURI);
2830             try {
2831 #endif
2832             return this.mCurrentBrowser.loadURIWithFlags(aURI, aFlags, aReferrerURI, aCharset, aPostData);
2833 #ifdef MAKE_E10S_WORK
2834             } catch (e) {
2835               let url = this.mCurrentBrowser.currentURI.spec;
2836               this.updateBrowserRemotenessByURL(this.mCurrentBrowser, url);
2837               throw e;
2838             }
2839 #endif
2840           ]]>
2841         </body>
2842       </method>
2844       <method name="goHome">
2845         <body>
2846           <![CDATA[
2847             return this.mCurrentBrowser.goHome();
2848           ]]>
2849         </body>
2850       </method>
2852       <property name="homePage">
2853         <getter>
2854           <![CDATA[
2855             return this.mCurrentBrowser.homePage;
2856           ]]>
2857         </getter>
2858         <setter>
2859           <![CDATA[
2860             this.mCurrentBrowser.homePage = val;
2861             return val;
2862           ]]>
2863         </setter>
2864       </property>
2866       <method name="gotoIndex">
2867         <parameter name="aIndex"/>
2868         <body>
2869           <![CDATA[
2870             return this.mCurrentBrowser.gotoIndex(aIndex);
2871           ]]>
2872         </body>
2873       </method>
2875       <method name="attachFormFill">
2876         <body><![CDATA[
2877           for (var i = 0; i < this.mPanelContainer.childNodes.length; ++i) {
2878             var cb = this.getBrowserAtIndex(i);
2879             cb.attachFormFill();
2880           }
2881         ]]></body>
2882       </method>
2884       <method name="detachFormFill">
2885         <body><![CDATA[
2886           for (var i = 0; i < this.mPanelContainer.childNodes.length; ++i) {
2887             var cb = this.getBrowserAtIndex(i);
2888             cb.detachFormFill();
2889           }
2890         ]]></body>
2891       </method>
2893       <property name="currentURI"
2894                 onget="return this.mCurrentBrowser.currentURI;"
2895                 readonly="true"/>
2897       <property name="finder"
2898                 onget="return this.mCurrentBrowser.finder"
2899                 readonly="true"/>
2901       <property name="docShell"
2902                 onget="return this.mCurrentBrowser.docShell"
2903                 readonly="true"/>
2905       <property name="webNavigation"
2906                 onget="return this.mCurrentBrowser.webNavigation"
2907                 readonly="true"/>
2909       <property name="webBrowserFind"
2910                 readonly="true"
2911                 onget="return this.mCurrentBrowser.webBrowserFind"/>
2913       <property name="webProgress"
2914                 readonly="true"
2915                 onget="return this.mCurrentBrowser.webProgress"/>
2917       <property name="contentWindow"
2918                 readonly="true"
2919                 onget="return this.mCurrentBrowser.contentWindow"/>
2921       <property name="sessionHistory"
2922                 onget="return this.mCurrentBrowser.sessionHistory;"
2923                 readonly="true"/>
2925       <property name="markupDocumentViewer"
2926                 onget="return this.mCurrentBrowser.markupDocumentViewer;"
2927                 readonly="true"/>
2929       <property name="contentViewerEdit"
2930                 onget="return this.mCurrentBrowser.contentViewerEdit;"
2931                 readonly="true"/>
2933       <property name="contentViewerFile"
2934                 onget="return this.mCurrentBrowser.contentViewerFile;"
2935                 readonly="true"/>
2937       <property name="contentDocument"
2938                 onget="return this.mCurrentBrowser.contentDocument;"
2939                 readonly="true"/>
2941       <property name="contentTitle"
2942                 onget="return this.mCurrentBrowser.contentTitle;"
2943                 readonly="true"/>
2945       <property name="contentPrincipal"
2946                 onget="return this.mCurrentBrowser.contentPrincipal;"
2947                 readonly="true"/>
2949       <property name="securityUI"
2950                 onget="return this.mCurrentBrowser.securityUI;"
2951                 readonly="true"/>
2953       <property name="fullZoom"
2954                 onget="return this.mCurrentBrowser.fullZoom;"
2955                 onset="this.mCurrentBrowser.fullZoom = val;"/>
2957       <property name="textZoom"
2958                 onget="return this.mCurrentBrowser.textZoom;"
2959                 onset="this.mCurrentBrowser.textZoom = val;"/>
2961       <property name="isSyntheticDocument"
2962                 onget="return this.mCurrentBrowser.isSyntheticDocument;"
2963                 readonly="true"/>
2965       <method name="_handleKeyDownEvent">
2966         <parameter name="aEvent"/>
2967         <body><![CDATA[
2968           if (!aEvent.isTrusted) {
2969             // Don't let untrusted events mess with tabs.
2970             return;
2971           }
2973           if (aEvent.altKey)
2974             return;
2976           // Don't check if the event was already consumed because tab
2977           // navigation should always work for better user experience.
2979           if (aEvent.ctrlKey && aEvent.shiftKey && !aEvent.metaKey) {
2980             switch (aEvent.keyCode) {
2981               case aEvent.DOM_VK_PAGE_UP:
2982                 this.moveTabBackward();
2983                 aEvent.preventDefault();
2984                 return;
2985               case aEvent.DOM_VK_PAGE_DOWN:
2986                 this.moveTabForward();
2987                 aEvent.preventDefault();
2988                 return;
2989             }
2990           }
2992 #ifndef XP_MACOSX
2993           if (aEvent.ctrlKey && !aEvent.shiftKey && !aEvent.metaKey &&
2994               aEvent.keyCode == KeyEvent.DOM_VK_F4 &&
2995               !this.mCurrentTab.pinned) {
2996             this.removeCurrentTab({animate: true});
2997             aEvent.preventDefault();
2998           }
2999 #endif
3000         ]]></body>
3001       </method>
3003       <method name="_handleKeyPressEvent">
3004         <parameter name="aEvent"/>
3005         <body><![CDATA[
3006           if (!aEvent.isTrusted) {
3007             // Don't let untrusted events mess with tabs.
3008             return;
3009           }
3011           if (aEvent.altKey)
3012             return;
3014           // We need to take care of FAYT-watching as long as the findbar
3015           // isn't initialized.  The checks on aEvent are copied from
3016           // _shouldFastFind (see findbar.xml).
3017           if (!gFindBarInitialized &&
3018               !(aEvent.ctrlKey || aEvent.metaKey) &&
3019               !aEvent.defaultPrevented) {
3020             let charCode = aEvent.charCode;
3021             if (charCode) {
3022               let char = String.fromCharCode(charCode);
3023               if (char == "'" || char == "/" ||
3024                   Services.prefs.getBoolPref("accessibility.typeaheadfind")) {
3025                 gFindBar._onBrowserKeypress(aEvent);
3026                 return;
3027               }
3028             }
3029           }
3031 #ifdef XP_MACOSX
3032           if (!aEvent.metaKey)
3033             return;
3035           var offset = 1;
3036           switch (aEvent.charCode) {
3037             case '}'.charCodeAt(0):
3038               offset = -1;
3039             case '{'.charCodeAt(0):
3040               if (window.getComputedStyle(this, null).direction == "ltr")
3041                 offset *= -1;
3042               this.tabContainer.advanceSelectedTab(offset, true);
3043               aEvent.preventDefault();
3044           }
3045 #endif
3046         ]]></body>
3047       </method>
3049       <property name="userTypedClear"
3050                 onget="return this.mCurrentBrowser.userTypedClear;"
3051                 onset="return this.mCurrentBrowser.userTypedClear = val;"/>
3053       <property name="userTypedValue"
3054                 onget="return this.mCurrentBrowser.userTypedValue;"
3055                 onset="return this.mCurrentBrowser.userTypedValue = val;"/>
3057       <method name="createTooltip">
3058         <parameter name="event"/>
3059         <body><![CDATA[
3060           event.stopPropagation();
3061           var tab = document.tooltipNode;
3062           if (tab.localName != "tab") {
3063             event.preventDefault();
3064             return;
3065           }
3066           event.target.setAttribute("label", tab.mOverCloseButton ?
3067                                              tab.getAttribute("closetabtext") :
3068                                              tab.getAttribute("label"));
3069         ]]></body>
3070       </method>
3072       <method name="handleEvent">
3073         <parameter name="aEvent"/>
3074         <body><![CDATA[
3075           switch (aEvent.type) {
3076             case "keydown":
3077               this._handleKeyDownEvent(aEvent);
3078               break;
3079             case "keypress":
3080               this._handleKeyPressEvent(aEvent);
3081               break;
3082             case "sizemodechange":
3083               if (aEvent.target == window) {
3084                 this.mCurrentBrowser.docShellIsActive =
3085                   (window.windowState != window.STATE_MINIMIZED);
3086               }
3087               break;
3088           }
3089         ]]></body>
3090       </method>
3092       <method name="receiveMessage">
3093         <parameter name="aMessage"/>
3094         <body><![CDATA[
3095           let json = aMessage.json;
3096           let browser = aMessage.target;
3098           switch (aMessage.name) {
3099             case "DOMTitleChanged": {
3100               let tab = this._getTabForBrowser(browser);
3101               if (!tab || tab.hasAttribute("pending"))
3102                 return;
3103               let titleChanged = this.setTabTitle(tab);
3104               if (titleChanged && !tab.selected && !tab.hasAttribute("busy"))
3105                 tab.setAttribute("titlechanged", "true");
3106               break;
3107             }
3108             case "DOMWindowClose": {
3109               if (this.tabs.length == 1) {
3110                 window.close();
3111                 return;
3112               }
3114               let tab = this._getTabForBrowser(browser);
3115               if (tab) {
3116                 this.removeTab(tab);
3117               }
3118               break;
3119             }
3120             case "contextmenu": {
3121               gContextMenuContentData = { event: aMessage.objects.event,
3122                                           browser: browser };
3123               let popup = browser.ownerDocument.getElementById("contentAreaContextMenu");
3124               let event = gContextMenuContentData.event;
3125               let pos = browser.mapScreenCoordinatesFromContent(event.screenX, event.screenY);
3126               popup.openPopupAtScreen(pos.x, pos.y, true);
3127               break;
3128             }
3129             case "DOMWebNotificationClicked": {
3130               let tab = this._getTabForBrowser(browser);
3131               if (!tab)
3132                 return;
3133               this.selectedTab = tab;
3134               window.focus();
3135               break;
3136             }
3137           }
3138         ]]></body>
3139       </method>
3141       <constructor>
3142         <![CDATA[
3143           let browserStack = document.getAnonymousElementByAttribute(this, "anonid", "browserStack");
3144           this.mCurrentBrowser = document.getAnonymousElementByAttribute(this, "anonid", "initialBrowser");
3146           this.mCurrentTab = this.tabContainer.firstChild;
3147           const nsIEventListenerService =
3148             Components.interfaces.nsIEventListenerService;
3149           let els = Components.classes["@mozilla.org/eventlistenerservice;1"]
3150                               .getService(nsIEventListenerService);
3151           els.addSystemEventListener(document, "keydown", this, false);
3152           els.addSystemEventListener(document, "keypress", this, false);
3153           window.addEventListener("sizemodechange", this, false);
3155           var uniqueId = this._generateUniquePanelID();
3156           this.mPanelContainer.childNodes[0].id = uniqueId;
3157           this.mCurrentTab.linkedPanel = uniqueId;
3158           this.mCurrentTab._tPos = 0;
3159           this.mCurrentTab._fullyOpen = true;
3160           this.mCurrentTab.linkedBrowser = this.mCurrentBrowser;
3162           // set up the shared autoscroll popup
3163           this._autoScrollPopup = this.mCurrentBrowser._createAutoScrollPopup();
3164           this._autoScrollPopup.id = "autoscroller";
3165           this.appendChild(this._autoScrollPopup);
3166           this.mCurrentBrowser.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
3167           this.mCurrentBrowser.droppedLinkHandler = handleDroppedLink;
3168           this.updateWindowResizers();
3170           // Hook up the event listeners to the first browser
3171           var tabListener = this.mTabProgressListener(this.mCurrentTab, this.mCurrentBrowser, true);
3172           const nsIWebProgress = Components.interfaces.nsIWebProgress;
3173           const filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
3174                                    .createInstance(nsIWebProgress);
3175           filter.addProgressListener(tabListener, nsIWebProgress.NOTIFY_ALL);
3176           this.mTabListeners[0] = tabListener;
3177           this.mTabFilters[0] = filter;
3178           this.webProgress.addProgressListener(filter, nsIWebProgress.NOTIFY_ALL);
3180           this.style.backgroundColor =
3181             Services.prefs.getBoolPref("browser.display.use_system_colors") ?
3182               "-moz-default-background-color" :
3183               Services.prefs.getCharPref("browser.display.background_color");
3185           let remote = window.QueryInterface(Ci.nsIInterfaceRequestor)
3186             .getInterface(Ci.nsIWebNavigation)
3187             .QueryInterface(Ci.nsILoadContext)
3188             .useRemoteTabs;
3189           if (remote) {
3190             messageManager.addMessageListener("DOMTitleChanged", this);
3191             messageManager.addMessageListener("DOMWindowClose", this);
3192             messageManager.addMessageListener("contextmenu", this);
3194             // If this window has remote tabs, switch to our tabpanels fork
3195             // which does asynchronous tab switching.
3196             this.mPanelContainer.classList.add("tabbrowser-tabpanels");
3197           }
3198           messageManager.addMessageListener("DOMWebNotificationClicked", this);
3199         ]]>
3200       </constructor>
3202       <method name="_generateUniquePanelID">
3203         <body><![CDATA[
3204           if (!this._uniquePanelIDCounter) {
3205             this._uniquePanelIDCounter = 0;
3206           }
3208           let outerID = window.QueryInterface(Ci.nsIInterfaceRequestor)
3209                               .getInterface(Ci.nsIDOMWindowUtils)
3210                               .outerWindowID;
3212           // We want panel IDs to be globally unique, that's why we include the
3213           // window ID. We switched to a monotonic counter as Date.now() lead
3214           // to random failures because of colliding IDs.
3215           return "panel-" + outerID + "-" + (++this._uniquePanelIDCounter);
3216         ]]></body>
3217       </method>
3219       <destructor>
3220         <![CDATA[
3221           for (var i = 0; i < this.mTabListeners.length; ++i) {
3222             let browser = this.getBrowserAtIndex(i);
3223             if (browser.registeredOpenURI) {
3224               this._placesAutocomplete.unregisterOpenPage(browser.registeredOpenURI);
3225               this._unifiedComplete.unregisterOpenPage(browser.registeredOpenURI);
3226               delete browser.registeredOpenURI;
3227             }
3228             browser.webProgress.removeProgressListener(this.mTabFilters[i]);
3229             this.mTabFilters[i].removeProgressListener(this.mTabListeners[i]);
3230             this.mTabFilters[i] = null;
3231             this.mTabListeners[i].destroy();
3232             this.mTabListeners[i] = null;
3233           }
3234           const nsIEventListenerService =
3235             Components.interfaces.nsIEventListenerService;
3236           let els = Components.classes["@mozilla.org/eventlistenerservice;1"]
3237                               .getService(nsIEventListenerService);
3238           els.removeSystemEventListener(document, "keydown", this, false);
3239           els.removeSystemEventListener(document, "keypress", this, false);
3240           window.removeEventListener("sizemodechange", this, false);
3242           if (gMultiProcessBrowser) {
3243             messageManager.removeMessageListener("DOMTitleChanged", this);
3244             messageManager.removeMessageListener("contextmenu", this);
3245           }
3246         ]]>
3247       </destructor>
3249       <!-- Deprecated stuff, implemented for backwards compatibility. -->
3250       <method name="enterTabbedMode">
3251         <body>
3252           Application.console.log("enterTabbedMode is an obsolete method and " +
3253                                   "will be removed in a future release.");
3254         </body>
3255       </method>
3256       <field name="mTabbedMode" readonly="true">true</field>
3257       <method name="setStripVisibilityTo">
3258         <parameter name="aShow"/>
3259         <body>
3260           this.tabContainer.visible = aShow;
3261         </body>
3262       </method>
3263       <method name="getStripVisibility">
3264         <body>
3265           return this.tabContainer.visible;
3266         </body>
3267       </method>
3269       <method name="_prepareForTabSwitch">
3270         <parameter name="toTab"/>
3271         <parameter name="fromTab"/>
3272         <body><![CDATA[
3273           const kTabSwitchTimeout = 300;
3274           let toBrowser = this.getBrowserForTab(toTab);
3275           let fromBrowser = fromTab ? this.getBrowserForTab(fromTab)
3276                                     : null;
3278           // We only want to wait for the MozAfterRemotePaint event if
3279           // the tab we're switching to is a remote tab, and if the tab
3280           // we're switching to isn't the one we've already got. The latter
3281           // case can occur when closing tabs before the currently selected
3282           // one.
3283           let shouldWait = toBrowser.getAttribute("remote") == "true" &&
3284                            toBrowser != fromBrowser;
3286           let switchPromise;
3288           if (shouldWait) {
3289             let timeoutId;
3290             let panels = this.mPanelContainer;
3292             // Both the timeout and MozAfterPaint promises use this same
3293             // logic to determine whether they should carry out the tab
3294             // switch, or reject it outright.
3295             let attemptTabSwitch = (aResolve, aReject) => {
3296               if (this.selectedBrowser == toBrowser) {
3297                 aResolve();
3298               } else {
3299                 // We switched away or closed the browser before we timed
3300                 // out. We reject, which will cancel the tab switch.
3301                 aReject();
3302               }
3303             };
3305             let timeoutPromise = new Promise((aResolve, aReject) => {
3306               timeoutId = setTimeout(() => {
3307                 attemptTabSwitch(aResolve, aReject);
3308               }, kTabSwitchTimeout);
3309             });
3311             let paintPromise = new Promise((aResolve, aReject) => {
3312               toBrowser.addEventListener("MozAfterRemotePaint", function onRemotePaint() {
3313                 toBrowser.removeEventListener("MozAfterRemotePaint", onRemotePaint);
3314                 clearTimeout(timeoutId);
3315                 attemptTabSwitch(aResolve, aReject);
3316               });
3317               toBrowser.QueryInterface(Ci.nsIFrameLoaderOwner)
3318                        .frameLoader
3319                        .requestNotifyAfterRemotePaint();
3320                // We need to activate the docShell on the tab we're switching
3321                // to - otherwise, we won't initiate a remote paint request and
3322                // therefore we won't get the MozAfterRemotePaint event that we're
3323                // waiting for.
3324                // Note that this happens, as we require, even if the timeout in the
3325                // timeoutPromise triggers before the paintPromise even runs.
3326                toBrowser.docShellIsActive = true;
3327             });
3329             switchPromise = Promise.race([paintPromise, timeoutPromise]);
3330           } else {
3331             // Activate the docShell on the tab we're switching to.
3332             toBrowser.docShellIsActive = true;
3334             // No need to wait - just resolve immediately to do the switch ASAP.
3335             switchPromise = Promise.resolve();
3336           }
3338           return switchPromise;
3339         ]]></body>
3340       </method>
3342       <method name="_deactivateContent">
3343         <parameter name="tab"/>
3344         <body><![CDATA[
3345           // It's unlikely, yet possible, that while we were waiting
3346           // to deactivate this tab, that something closed it and wiped
3347           // out the browser. For example, during a tab switch, while waiting
3348           // for the MozAfterRemotePaint event to fire, something closes the
3349           // original tab that the user had selected. If that's the case, then
3350           // there's nothing to deactivate.
3351           let browser = this.getBrowserForTab(tab);
3352           if (browser && this.selectedBrowser != browser) {
3353             browser.docShellIsActive = false;
3354           }
3355         ]]></body>
3356       </method>
3358       <method name="_finalizeTabSwitch">
3359         <parameter name="toTab"/>
3360         <parameter name="fromTab"/>
3361         <body><![CDATA[
3362           this._adjustFocusAfterTabSwitch(toTab, fromTab);
3363           this._deactivateContent(fromTab);
3365           let toBrowser = this.getBrowserForTab(toTab);
3366           toBrowser.setAttribute("type", "content-primary");
3368           let fromBrowser = this.getBrowserForTab(fromTab);
3369           // It's possible that the tab we're switching from closed
3370           // before we were able to finalize, in which case, fromBrowser
3371           // doesn't exist.
3372           if (fromBrowser) {
3373             fromBrowser.setAttribute("type", "content-targetable");
3374           }
3375         ]]></body>
3376       </method>
3378       <method name="_cancelTabSwitch">
3379         <parameter name="toTab"/>
3380         <body><![CDATA[
3381           this._deactivateContent(toTab);
3382         ]]></body>
3383       </method>
3385       <property name="mContextTab" readonly="true"
3386                 onget="return TabContextMenu.contextTab;"/>
3387       <property name="mPrefs" readonly="true"
3388                 onget="return Services.prefs;"/>
3389       <property name="mTabContainer" readonly="true"
3390                 onget="return this.tabContainer;"/>
3391       <property name="mTabs" readonly="true"
3392                 onget="return this.tabs;"/>
3393       <!--
3394         - Compatibility hack: several extensions depend on this property to
3395         - access the tab context menu or tab container, so keep that working for
3396         - now. Ideally we can remove this once extensions are using
3397         - tabbrowser.tabContextMenu and tabbrowser.tabContainer directly.
3398         -->
3399       <property name="mStrip" readonly="true">
3400         <getter>
3401         <![CDATA[
3402           return ({
3403             self: this,
3404             childNodes: [null, this.tabContextMenu, this.tabContainer],
3405             firstChild: { nextSibling: this.tabContextMenu },
3406             getElementsByAttribute: function (attr, attrValue) {
3407               if (attr == "anonid" && attrValue == "tabContextMenu")
3408                 return [this.self.tabContextMenu];
3409               return [];
3410             },
3411             // Also support adding event listeners (forward to the tab container)
3412             addEventListener: function (a,b,c) { this.self.tabContainer.addEventListener(a,b,c); },
3413             removeEventListener: function (a,b,c) { this.self.tabContainer.removeEventListener(a,b,c); }
3414           });
3415         ]]>
3416         </getter>
3417       </property>
3418     </implementation>
3420     <handlers>
3421       <handler event="DOMWindowClose" phase="capturing">
3422         <![CDATA[
3423           if (!event.isTrusted)
3424             return;
3426           if (this.tabs.length == 1)
3427             return;
3429           var tab = this._getTabForContentWindow(event.target);
3430           if (tab) {
3431             this.removeTab(tab);
3432             event.preventDefault();
3433           }
3434         ]]>
3435       </handler>
3436       <handler event="DOMWillOpenModalDialog" phase="capturing">
3437         <![CDATA[
3438           if (!event.isTrusted)
3439             return;
3441           // We're about to open a modal dialog, make sure the opening
3442           // tab is brought to the front.
3443           // If this is a same-process modal dialog, then we're given its DOM
3444           // window as the event's target. For remote dialogs, we're given the
3445           // browser, but that's in the originalTarget.
3446           // XXX Why originalTarget for the browser?
3447           this.selectedTab = (event.target instanceof Window) ?
3448                                this._getTabForContentWindow(event.target.top) :
3449                                this._getTabForBrowser(event.originalTarget);
3450         ]]>
3451       </handler>
3452       <handler event="DOMTitleChanged">
3453         <![CDATA[
3454           if (!event.isTrusted)
3455             return;
3457           var contentWin = event.target.defaultView;
3458           if (contentWin != contentWin.top)
3459             return;
3461           var tab = this._getTabForContentWindow(contentWin);
3462           if (tab.hasAttribute("pending"))
3463             return;
3465           var titleChanged = this.setTabTitle(tab);
3466           if (titleChanged && !tab.selected && !tab.hasAttribute("busy"))
3467             tab.setAttribute("titlechanged", "true");
3468         ]]>
3469       </handler>
3470       <handler event="oop-browser-crashed">
3471         <![CDATA[
3472           if (!event.isTrusted)
3473             return;
3475           let browser = event.originalTarget;
3476           let title = browser.contentTitle;
3477           let uri = browser.currentURI;
3478           let icon = browser.mIconURL;
3480           this.updateBrowserRemotenessByURL(browser, "about:tabcrashed");
3482           browser.setAttribute("crashedPageTitle", title);
3483           browser.docShell.displayLoadError(Cr.NS_ERROR_CONTENT_CRASHED, uri, null);
3484           browser.removeAttribute("crashedPageTitle");
3485           let tab = this._getTabForBrowser(browser);
3486           this.setIcon(tab, icon);
3487         ]]>
3488       </handler>
3489     </handlers>
3490   </binding>
3492   <binding id="tabbrowser-tabbox"
3493            extends="chrome://global/content/bindings/tabbox.xml#tabbox">
3494     <implementation>
3495       <property name="tabs" readonly="true"
3496                 onget="return document.getBindingParent(this).tabContainer;"/>
3497     </implementation>
3498   </binding>
3500   <binding id="tabbrowser-arrowscrollbox" extends="chrome://global/content/bindings/scrollbox.xml#arrowscrollbox-clicktoscroll">
3501     <implementation>
3502       <!-- Override scrollbox.xml method, since our scrollbox's children are
3503            inherited from the binding parent -->
3504       <method name="_getScrollableElements">
3505         <body><![CDATA[
3506           return Array.filter(document.getBindingParent(this).childNodes,
3507                               this._canScrollToElement, this);
3508         ]]></body>
3509       </method>
3510       <method name="_canScrollToElement">
3511         <parameter name="tab"/>
3512         <body><![CDATA[
3513           return !tab.pinned && !tab.hidden;
3514         ]]></body>
3515       </method>
3516       <field name="_tabMarginLeft">null</field>
3517       <field name="_tabMarginRight">null</field>
3518       <method name="_calcTabMargins">
3519         <parameter name="aTab"/>
3520         <body><![CDATA[
3521           if (this._tabMarginLeft === null || this._tabMarginRight === null) {
3522             let tabMiddle = document.getAnonymousElementByAttribute(aTab, "class", "tab-background-middle");
3523             let tabMiddleStyle = window.getComputedStyle(tabMiddle, null);
3524             this._tabMarginLeft = parseFloat(tabMiddleStyle.marginLeft);
3525             this._tabMarginRight = parseFloat(tabMiddleStyle.marginRight);
3526           }
3527         ]]></body>
3528       </method>
3529       <method name="_adjustElementStartAndEnd">
3530         <parameter name="aTab"/>
3531         <parameter name="tabStart"/>
3532         <parameter name="tabEnd"/>
3533         <body><![CDATA[
3534           this._calcTabMargins(aTab);
3535           if (this._tabMarginLeft < 0) {
3536             tabStart = tabStart + this._tabMarginLeft;
3537           }
3538           if (this._tabMarginRight < 0) {
3539             tabEnd = tabEnd - this._tabMarginRight;
3540           }
3541           return [tabStart, tabEnd];
3542         ]]></body>
3543       </method>
3544     </implementation>
3546     <handlers>
3547       <handler event="underflow" phase="capturing"><![CDATA[
3548         if (event.detail == 0)
3549           return; // Ignore vertical events
3551         var tabs = document.getBindingParent(this);
3552         tabs.removeAttribute("overflow");
3554         if (tabs._lastTabClosedByMouse)
3555           tabs._expandSpacerBy(this._scrollButtonDown.clientWidth);
3557         tabs.tabbrowser._removingTabs.forEach(tabs.tabbrowser.removeTab,
3558                                               tabs.tabbrowser);
3560         tabs._positionPinnedTabs();
3561       ]]></handler>
3562       <handler event="overflow"><![CDATA[
3563         if (event.detail == 0)
3564           return; // Ignore vertical events
3566         var tabs = document.getBindingParent(this);
3567         tabs.setAttribute("overflow", "true");
3568         tabs._positionPinnedTabs();
3569         tabs._handleTabSelect(false);
3570       ]]></handler>
3571     </handlers>
3572   </binding>
3574   <binding id="tabbrowser-tabs"
3575            extends="chrome://global/content/bindings/tabbox.xml#tabs">
3576     <resources>
3577       <stylesheet src="chrome://browser/content/tabbrowser.css"/>
3578     </resources>
3580     <content>
3581       <xul:hbox align="end">
3582         <xul:image class="tab-drop-indicator" anonid="tab-drop-indicator" collapsed="true"/>
3583       </xul:hbox>
3584       <xul:arrowscrollbox anonid="arrowscrollbox" orient="horizontal" flex="1"
3585                           style="min-width: 1px;"
3586 #ifndef XP_MACOSX
3587                           clicktoscroll="true"
3588 #endif
3589                           class="tabbrowser-arrowscrollbox">
3590 # This is a hack to circumvent bug 472020, otherwise the tabs show up on the
3591 # right of the newtab button.
3592         <children includes="tab"/>
3593 # This is to ensure anything extensions put here will go before the newtab
3594 # button, necessary due to the previous hack.
3595         <children/>
3596         <xul:toolbarbutton class="tabs-newtab-button"
3597                            anonid="tabs-newtab-button"
3598                            command="cmd_newNavigatorTab"
3599                            onclick="checkForMiddleClick(this, event);"
3600                            onmouseover="document.getBindingParent(this)._enterNewTab();"
3601                            onmouseout="document.getBindingParent(this)._leaveNewTab();"
3602                            tooltip="dynamic-shortcut-tooltip"/>
3603         <xul:spacer class="closing-tabs-spacer" anonid="closing-tabs-spacer"
3604                     style="width: 0;"/>
3605       </xul:arrowscrollbox>
3606     </content>
3608     <implementation implements="nsIDOMEventListener">
3609       <constructor>
3610         <![CDATA[
3611           this.mTabClipWidth = Services.prefs.getIntPref("browser.tabs.tabClipWidth");
3613           var tab = this.firstChild;
3614           tab.label = this.tabbrowser.mStringBundle.getString("tabs.emptyTabTitle");
3615           tab.setAttribute("crop", "end");
3616           tab.setAttribute("onerror", "this.removeAttribute('image');");
3618           window.addEventListener("resize", this, false);
3619           window.addEventListener("load", this, false);
3621           try {
3622             this._tabAnimationLoggingEnabled = Services.prefs.getBoolPref("browser.tabs.animationLogging.enabled");
3623           } catch (ex) {
3624             this._tabAnimationLoggingEnabled = false;
3625           }
3626           this._browserNewtabpageEnabled = Services.prefs.getBoolPref("browser.newtabpage.enabled");
3627         ]]>
3628       </constructor>
3630       <field name="tabbrowser" readonly="true">
3631         document.getElementById(this.getAttribute("tabbrowser"));
3632       </field>
3634       <field name="tabbox" readonly="true">
3635         this.tabbrowser.mTabBox;
3636       </field>
3638       <field name="contextMenu" readonly="true">
3639         document.getElementById("tabContextMenu");
3640       </field>
3642       <field name="mTabstripWidth">0</field>
3644       <field name="mTabstrip">
3645         document.getAnonymousElementByAttribute(this, "anonid", "arrowscrollbox");
3646       </field>
3648       <field name="_firstTab">null</field>
3649       <field name="_lastTab">null</field>
3650       <field name="_afterSelectedTab">null</field>
3651       <field name="_beforeHoveredTab">null</field>
3652       <field name="_afterHoveredTab">null</field>
3653       <field name="_hoveredTab">null</field>
3655       <property name="_isCustomizing" readonly="true">
3656         <getter>
3657           let root = document.documentElement;
3658           return root.getAttribute("customizing") == "true" ||
3659                  root.getAttribute("customize-exiting") == "true";
3660         </getter>
3661       </property>
3663       <method name="_setPositionalAttributes">
3664         <body><![CDATA[
3665           let visibleTabs = this.tabbrowser.visibleTabs;
3667           if (!visibleTabs.length)
3668             return;
3670           let selectedIndex = visibleTabs.indexOf(this.selectedItem);
3672           let lastVisible = visibleTabs.length - 1;
3674           if (this._afterSelectedTab)
3675             this._afterSelectedTab.removeAttribute("afterselected-visible");
3676           if (this.selectedItem.closing || selectedIndex == lastVisible) {
3677             this._afterSelectedTab = null;
3678           } else {
3679             this._afterSelectedTab = visibleTabs[selectedIndex + 1];
3680             this._afterSelectedTab.setAttribute("afterselected-visible",
3681                                                 "true");
3682           }
3684           if (this._firstTab)
3685             this._firstTab.removeAttribute("first-visible-tab");
3686           this._firstTab = visibleTabs[0];
3687           this._firstTab.setAttribute("first-visible-tab", "true");
3688           if (this._lastTab)
3689             this._lastTab.removeAttribute("last-visible-tab");
3690           this._lastTab = visibleTabs[lastVisible];
3691           this._lastTab.setAttribute("last-visible-tab", "true");
3693           let hoveredTab = this._hoveredTab;
3694           if (hoveredTab) {
3695             hoveredTab._mouseleave();
3696             hoveredTab._mouseenter();
3697           }
3698         ]]></body>
3699       </method>
3701       <field name="_blockDblClick">false</field>
3703       <field name="_tabDropIndicator">
3704         document.getAnonymousElementByAttribute(this, "anonid", "tab-drop-indicator");
3705       </field>
3707       <field name="_dragOverDelay">350</field>
3708       <field name="_dragTime">0</field>
3710       <field name="_container" readonly="true"><![CDATA[
3711         this.parentNode && this.parentNode.localName == "toolbar" ? this.parentNode : this;
3712       ]]></field>
3714       <field name="_propagatedVisibilityOnce">false</field>
3716       <property name="visible"
3717                 onget="return !this._container.collapsed;">
3718         <setter><![CDATA[
3719           if (val == this.visible &&
3720               this._propagatedVisibilityOnce)
3721             return val;
3723           this._container.collapsed = !val;
3725           this._propagateVisibility();
3726           this._propagatedVisibilityOnce = true;
3728           return val;
3729         ]]></setter>
3730       </property>
3732       <method name="_enterNewTab">
3733         <body><![CDATA[
3734           let visibleTabs = this.tabbrowser.visibleTabs;
3735           let candidate = visibleTabs[visibleTabs.length - 1];
3736           if (!candidate.selected) {
3737             this._beforeHoveredTab = candidate;
3738             candidate.setAttribute("beforehovered", "true");
3739           }
3740         ]]></body>
3741       </method>
3743       <method name="_leaveNewTab">
3744         <body><![CDATA[
3745           if (this._beforeHoveredTab) {
3746             this._beforeHoveredTab.removeAttribute("beforehovered");
3747             this._beforeHoveredTab = null;
3748           }
3749         ]]></body>
3750       </method>
3752       <method name="_propagateVisibility">
3753         <body><![CDATA[
3754           let visible = this.visible;
3756           document.getElementById("menu_closeWindow").hidden = !visible;
3757           document.getElementById("menu_close").setAttribute("label",
3758             this.tabbrowser.mStringBundle.getString(visible ? "tabs.closeTab" : "tabs.close"));
3760           TabsInTitlebar.allowedBy("tabs-visible", visible);
3761         ]]></body>
3762       </method>
3764       <method name="updateVisibility">
3765         <body><![CDATA[
3766           if (this.childNodes.length - this.tabbrowser._removingTabs.length == 1)
3767             this.visible = window.toolbar.visible;
3768           else
3769             this.visible = true;
3770         ]]></body>
3771       </method>
3773       <method name="adjustTabstrip">
3774         <body><![CDATA[
3775           let numTabs = this.childNodes.length -
3776                         this.tabbrowser._removingTabs.length;
3777           if (numTabs > 2) {
3778             // This is an optimization to avoid layout flushes by calling
3779             // getBoundingClientRect() when we just opened a second tab. In
3780             // this case it's highly unlikely that the tab width is smaller
3781             // than mTabClipWidth and the tab close button obscures too much
3782             // of the tab's label. In the edge case of the window being too
3783             // narrow (or if tabClipWidth has been set to a way higher value),
3784             // we'll correct the 'closebuttons' attribute after the tabopen
3785             // animation has finished.
3787             let tab = this.tabbrowser.visibleTabs[this.tabbrowser._numPinnedTabs];
3788             if (tab && tab.getBoundingClientRect().width <= this.mTabClipWidth) {
3789               this.setAttribute("closebuttons", "activetab");
3790               return;
3791             }
3792           }
3793           this.removeAttribute("closebuttons");
3794         ]]></body>
3795       </method>
3797       <method name="_handleTabSelect">
3798         <parameter name="aSmoothScroll"/>
3799         <body><![CDATA[
3800           if (this.getAttribute("overflow") == "true")
3801             this.mTabstrip.ensureElementIsVisible(this.selectedItem, aSmoothScroll);
3802         ]]></body>
3803       </method>
3805       <method name="_fillTrailingGap">
3806         <body><![CDATA[
3807           try {
3808             // if we're at the right side (and not the logical end,
3809             // which is why this works for both LTR and RTL)
3810             // of the tabstrip, we need to ensure that we stay
3811             // completely scrolled to the right side
3812             var tabStrip = this.mTabstrip;
3813             if (tabStrip.scrollPosition + tabStrip.scrollClientSize >
3814                 tabStrip.scrollSize)
3815               tabStrip.scrollByPixels(-1);
3816           } catch (e) {}
3817         ]]></body>
3818       </method>
3820       <field name="_closingTabsSpacer">
3821         document.getAnonymousElementByAttribute(this, "anonid", "closing-tabs-spacer");
3822       </field>
3824       <field name="_tabDefaultMaxWidth">NaN</field>
3825       <field name="_lastTabClosedByMouse">false</field>
3826       <field name="_hasTabTempMaxWidth">false</field>
3828       <!-- Try to keep the active tab's close button under the mouse cursor -->
3829       <method name="_lockTabSizing">
3830         <parameter name="aTab"/>
3831         <body><![CDATA[
3832           var tabs = this.tabbrowser.visibleTabs;
3833           if (!tabs.length)
3834             return;
3836           var isEndTab = (aTab._tPos > tabs[tabs.length-1]._tPos);
3837           var tabWidth = aTab.getBoundingClientRect().width;
3839           if (!this._tabDefaultMaxWidth)
3840             this._tabDefaultMaxWidth =
3841               parseFloat(window.getComputedStyle(aTab).maxWidth);
3842           this._lastTabClosedByMouse = true;
3844           if (this.getAttribute("overflow") == "true") {
3845             // Don't need to do anything if we're in overflow mode and aren't scrolled
3846             // all the way to the right, or if we're closing the last tab.
3847             if (isEndTab || !this.mTabstrip._scrollButtonDown.disabled)
3848               return;
3850             // If the tab has an owner that will become the active tab, the owner will
3851             // be to the left of it, so we actually want the left tab to slide over.
3852             // This can't be done as easily in non-overflow mode, so we don't bother.
3853             if (aTab.owner)
3854               return;
3856             this._expandSpacerBy(tabWidth);
3857           } else { // non-overflow mode
3858             // Locking is neither in effect nor needed, so let tabs expand normally.
3859             if (isEndTab && !this._hasTabTempMaxWidth)
3860               return;
3862             let numPinned = this.tabbrowser._numPinnedTabs;
3863             // Force tabs to stay the same width, unless we're closing the last tab,
3864             // which case we need to let them expand just enough so that the overall
3865             // tabbar width is the same.
3866             if (isEndTab) {
3867               let numNormalTabs = tabs.length - numPinned;
3868               tabWidth = tabWidth * (numNormalTabs + 1) / numNormalTabs;
3869               if (tabWidth > this._tabDefaultMaxWidth)
3870                 tabWidth = this._tabDefaultMaxWidth;
3871             }
3872             tabWidth += "px";
3873             for (let i = numPinned; i < tabs.length; i++) {
3874               let tab = tabs[i];
3875               tab.style.setProperty("max-width", tabWidth, "important");
3876               if (!isEndTab) { // keep tabs the same width
3877                 tab.style.transition = "none";
3878                 tab.clientTop; // flush styles to skip animation; see bug 649247
3879                 tab.style.transition = "";
3880               }
3881             }
3882             this._hasTabTempMaxWidth = true;
3883             this.tabbrowser.addEventListener("mousemove", this, false);
3884             window.addEventListener("mouseout", this, false);
3885           }
3886         ]]></body>
3887       </method>
3889       <method name="_expandSpacerBy">
3890         <parameter name="pixels"/>
3891         <body><![CDATA[
3892           let spacer = this._closingTabsSpacer;
3893           spacer.style.width = parseFloat(spacer.style.width) + pixels + "px";
3894           this.setAttribute("using-closing-tabs-spacer", "true");
3895           this.tabbrowser.addEventListener("mousemove", this, false);
3896           window.addEventListener("mouseout", this, false);
3897         ]]></body>
3898       </method>
3900       <method name="_unlockTabSizing">
3901         <body><![CDATA[
3902           this.tabbrowser.removeEventListener("mousemove", this, false);
3903           window.removeEventListener("mouseout", this, false);
3905           if (this._hasTabTempMaxWidth) {
3906             this._hasTabTempMaxWidth = false;
3907             let tabs = this.tabbrowser.visibleTabs;
3908             for (let i = 0; i < tabs.length; i++)
3909               tabs[i].style.maxWidth = "";
3910           }
3912           if (this.hasAttribute("using-closing-tabs-spacer")) {
3913             this.removeAttribute("using-closing-tabs-spacer");
3914             this._closingTabsSpacer.style.width = 0;
3915           }
3916         ]]></body>
3917       </method>
3919       <field name="_lastNumPinned">0</field>
3920       <method name="_positionPinnedTabs">
3921         <body><![CDATA[
3922           var numPinned = this.tabbrowser._numPinnedTabs;
3923           var doPosition = this.getAttribute("overflow") == "true" &&
3924                            numPinned > 0;
3926           if (doPosition) {
3927             this.setAttribute("positionpinnedtabs", "true");
3929             let scrollButtonWidth = this.mTabstrip._scrollButtonDown.getBoundingClientRect().width;
3930             let paddingStart = this.mTabstrip.scrollboxPaddingStart;
3931             let width = 0;
3933             for (let i = numPinned - 1; i >= 0; i--) {
3934               let tab = this.childNodes[i];
3935               width += tab.getBoundingClientRect().width;
3936               tab.style.MozMarginStart = - (width + scrollButtonWidth + paddingStart) + "px";
3937             }
3939             this.style.MozPaddingStart = width + paddingStart + "px";
3941           } else {
3942             this.removeAttribute("positionpinnedtabs");
3944             for (let i = 0; i < numPinned; i++) {
3945               let tab = this.childNodes[i];
3946               tab.style.MozMarginStart = "";
3947             }
3949             this.style.MozPaddingStart = "";
3950           }
3952           if (this._lastNumPinned != numPinned) {
3953             this._lastNumPinned = numPinned;
3954             this._handleTabSelect(false);
3955           }
3956         ]]></body>
3957       </method>
3959       <method name="_animateTabMove">
3960         <parameter name="event"/>
3961         <body><![CDATA[
3962           let draggedTab = event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0);
3964           if (this.getAttribute("movingtab") != "true") {
3965             this.setAttribute("movingtab", "true");
3966             this.selectedItem = draggedTab;
3967           }
3969           if (!("animLastScreenX" in draggedTab._dragData))
3970             draggedTab._dragData.animLastScreenX = draggedTab._dragData.screenX;
3972           let screenX = event.screenX;
3973           if (screenX == draggedTab._dragData.animLastScreenX)
3974             return;
3976           let draggingRight = screenX > draggedTab._dragData.animLastScreenX;
3977           draggedTab._dragData.animLastScreenX = screenX;
3979           let rtl = (window.getComputedStyle(this).direction == "rtl");
3980           let pinned = draggedTab.pinned;
3981           let numPinned = this.tabbrowser._numPinnedTabs;
3982           let tabs = this.tabbrowser.visibleTabs
3983                                     .slice(pinned ? 0 : numPinned,
3984                                            pinned ? numPinned : undefined);
3985           if (rtl)
3986             tabs.reverse();
3987           let tabWidth = draggedTab.getBoundingClientRect().width;
3989           // Move the dragged tab based on the mouse position.
3991           let leftTab = tabs[0];
3992           let rightTab = tabs[tabs.length - 1];
3993           let tabScreenX = draggedTab.boxObject.screenX;
3994           let translateX = screenX - draggedTab._dragData.screenX;
3995           if (!pinned)
3996             translateX += this.mTabstrip.scrollPosition - draggedTab._dragData.scrollX;
3997           let leftBound = leftTab.boxObject.screenX - tabScreenX;
3998           let rightBound = (rightTab.boxObject.screenX + rightTab.boxObject.width) -
3999                            (tabScreenX + tabWidth);
4000           translateX = Math.max(translateX, leftBound);
4001           translateX = Math.min(translateX, rightBound);
4002           draggedTab.style.transform = "translateX(" + translateX + "px)";
4004           // Determine what tab we're dragging over.
4005           // * Point of reference is the center of the dragged tab. If that
4006           //   point touches a background tab, the dragged tab would take that
4007           //   tab's position when dropped.
4008           // * We're doing a binary search in order to reduce the amount of
4009           //   tabs we need to check.
4011           let tabCenter = tabScreenX + translateX + tabWidth / 2;
4012           let newIndex = -1;
4013           let oldIndex = "animDropIndex" in draggedTab._dragData ?
4014                          draggedTab._dragData.animDropIndex : draggedTab._tPos;
4015           let low = 0;
4016           let high = tabs.length - 1;
4017           while (low <= high) {
4018             let mid = Math.floor((low + high) / 2);
4019             if (tabs[mid] == draggedTab &&
4020                 ++mid > high)
4021               break;
4022             let boxObject = tabs[mid].boxObject;
4023             let screenX = boxObject.screenX + getTabShift(tabs[mid], oldIndex);
4024             if (screenX > tabCenter) {
4025               high = mid - 1;
4026             } else if (screenX + boxObject.width < tabCenter) {
4027               low = mid + 1;
4028             } else {
4029               newIndex = tabs[mid]._tPos;
4030               break;
4031             }
4032           }
4033           if (newIndex >= oldIndex)
4034             newIndex++;
4035           if (newIndex < 0 || newIndex == oldIndex)
4036             return;
4037           draggedTab._dragData.animDropIndex = newIndex;
4039           // Shift background tabs to leave a gap where the dragged tab
4040           // would currently be dropped.
4042           for (let tab of tabs) {
4043             if (tab != draggedTab) {
4044               let shift = getTabShift(tab, newIndex);
4045               tab.style.transform = shift ? "translateX(" + shift + "px)" : "";
4046             }
4047           }
4049           function getTabShift(tab, dropIndex) {
4050             if (tab._tPos < draggedTab._tPos && tab._tPos >= dropIndex)
4051               return rtl ? -tabWidth : tabWidth;
4052             if (tab._tPos > draggedTab._tPos && tab._tPos < dropIndex)
4053               return rtl ? tabWidth : -tabWidth;
4054             return 0;
4055           }
4056         ]]></body>
4057       </method>
4059       <method name="_finishAnimateTabMove">
4060         <body><![CDATA[
4061           if (this.getAttribute("movingtab") != "true")
4062             return;
4064           for (let tab of this.tabbrowser.visibleTabs)
4065             tab.style.transform = "";
4067           this.removeAttribute("movingtab");
4069           this._handleTabSelect();
4070         ]]></body>
4071       </method>
4073       <method name="handleEvent">
4074         <parameter name="aEvent"/>
4075         <body><![CDATA[
4076           switch (aEvent.type) {
4077             case "load":
4078               this.updateVisibility();
4079               break;
4080             case "resize":
4081               if (aEvent.target != window)
4082                 break;
4084               TabsInTitlebar.updateAppearance();
4086               var width = this.mTabstrip.boxObject.width;
4087               if (width != this.mTabstripWidth) {
4088                 this.adjustTabstrip();
4089                 this._fillTrailingGap();
4090                 this._handleTabSelect();
4091                 this.mTabstripWidth = width;
4092               }
4094               this.tabbrowser.updateWindowResizers();
4095               break;
4096             case "mouseout":
4097               // If the "related target" (the node to which the pointer went) is not
4098               // a child of the current document, the mouse just left the window.
4099               let relatedTarget = aEvent.relatedTarget;
4100               if (relatedTarget && relatedTarget.ownerDocument == document)
4101                 break;
4102             case "mousemove":
4103               if (document.getElementById("tabContextMenu").state != "open")
4104                 this._unlockTabSizing();
4105               break;
4106           }
4107         ]]></body>
4108       </method>
4110       <field name="_animateElement">
4111         this.mTabstrip._scrollButtonDown;
4112       </field>
4114       <method name="_notifyBackgroundTab">
4115         <parameter name="aTab"/>
4116         <body><![CDATA[
4117           if (aTab.pinned)
4118             return;
4120           var scrollRect = this.mTabstrip.scrollClientRect;
4121           var tab = aTab.getBoundingClientRect();
4122           this.mTabstrip._calcTabMargins(aTab);
4124           // DOMRect left/right properties are immutable.
4125           tab = {left: tab.left, right: tab.right};
4127           // Is the new tab already completely visible?
4128           if (scrollRect.left <= tab.left && tab.right <= scrollRect.right)
4129             return;
4131           if (this.mTabstrip.smoothScroll) {
4132             let selected = !this.selectedItem.pinned &&
4133                            this.selectedItem.getBoundingClientRect();
4134             if (selected) {
4135               selected = {left: selected.left, right: selected.right};
4136               // Need to take in to account the width of the left/right margins on tabs.
4137               selected.left = selected.left + this.mTabstrip._tabMarginLeft;
4138               selected.right = selected.right - this.mTabstrip._tabMarginRight;
4139             }
4141             tab.left += this.mTabstrip._tabMarginLeft;
4142             tab.right -= this.mTabstrip._tabMarginRight;
4144             // Can we make both the new tab and the selected tab completely visible?
4145             if (!selected ||
4146                 Math.max(tab.right - selected.left, selected.right - tab.left) <=
4147                   scrollRect.width) {
4148               this.mTabstrip.ensureElementIsVisible(aTab);
4149               return;
4150             }
4152             this.mTabstrip._smoothScrollByPixels(this.mTabstrip._isRTLScrollbox ?
4153                                                  selected.right - scrollRect.right :
4154                                                  selected.left - scrollRect.left);
4155           }
4157           if (!this._animateElement.hasAttribute("notifybgtab")) {
4158             this._animateElement.setAttribute("notifybgtab", "true");
4159             setTimeout(function (ele) {
4160               ele.removeAttribute("notifybgtab");
4161             }, 150, this._animateElement);
4162           }
4163         ]]></body>
4164       </method>
4166       <method name="_getDragTargetTab">
4167         <parameter name="event"/>
4168         <body><![CDATA[
4169           let tab = event.target.localName == "tab" ? event.target : null;
4170           if (tab &&
4171               (event.type == "drop" || event.type == "dragover") &&
4172               event.dataTransfer.dropEffect == "link") {
4173             let boxObject = tab.boxObject;
4174             if (event.screenX < boxObject.screenX + boxObject.width * .25 ||
4175                 event.screenX > boxObject.screenX + boxObject.width * .75)
4176               return null;
4177           }
4178           return tab;
4179         ]]></body>
4180       </method>
4182       <method name="_getDropIndex">
4183         <parameter name="event"/>
4184         <body><![CDATA[
4185           var tabs = this.childNodes;
4186           var tab = this._getDragTargetTab(event);
4187           if (window.getComputedStyle(this, null).direction == "ltr") {
4188             for (let i = tab ? tab._tPos : 0; i < tabs.length; i++)
4189               if (event.screenX < tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2)
4190                 return i;
4191           } else {
4192             for (let i = tab ? tab._tPos : 0; i < tabs.length; i++)
4193               if (event.screenX > tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2)
4194                 return i;
4195           }
4196           return tabs.length;
4197         ]]></body>
4198       </method>
4200       <method name="_setEffectAllowedForDataTransfer">
4201         <parameter name="event"/>
4202         <body><![CDATA[
4203           var dt = event.dataTransfer;
4204           // Disallow dropping multiple items
4205           if (dt.mozItemCount > 1)
4206             return dt.effectAllowed = "none";
4208           var types = dt.mozTypesAt(0);
4209           var sourceNode = null;
4210           // tabs are always added as the first type
4211           if (types[0] == TAB_DROP_TYPE) {
4212             var sourceNode = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
4213             if (sourceNode instanceof XULElement &&
4214                 sourceNode.localName == "tab" &&
4215                 sourceNode.ownerDocument.defaultView instanceof ChromeWindow &&
4216                 sourceNode.ownerDocument.documentElement.getAttribute("windowtype") == "navigator:browser" &&
4217                 sourceNode.ownerDocument.defaultView.gBrowser.tabContainer == sourceNode.parentNode) {
4218               // Do not allow transfering a private tab to a non-private window
4219               // and vice versa.
4220               if (PrivateBrowsingUtils.isWindowPrivate(window) !=
4221                   PrivateBrowsingUtils.isWindowPrivate(sourceNode.ownerDocument.defaultView))
4222                 return dt.effectAllowed = "none";
4224               if (window.gMultiProcessBrowser !=
4225                   sourceNode.ownerDocument.defaultView.gMultiProcessBrowser)
4226                 return dt.effectAllowed = "none";
4228 #ifdef XP_MACOSX
4229               return dt.effectAllowed = event.altKey ? "copy" : "move";
4230 #else
4231               return dt.effectAllowed = event.ctrlKey ? "copy" : "move";
4232 #endif
4233             }
4234           }
4236           if (browserDragAndDrop.canDropLink(event)) {
4237             // Here we need to do this manually
4238             return dt.effectAllowed = dt.dropEffect = "link";
4239           }
4240           return dt.effectAllowed = "none";
4241         ]]></body>
4242       </method>
4244       <method name="_handleNewTab">
4245         <parameter name="tab"/>
4246         <body><![CDATA[
4247           if (tab.parentNode != this)
4248             return;
4249           tab._fullyOpen = true;
4251           this.adjustTabstrip();
4253           if (tab.getAttribute("selected") == "true") {
4254             this._fillTrailingGap();
4255             this._handleTabSelect();
4256           } else {
4257             this._notifyBackgroundTab(tab);
4258           }
4260           // XXXmano: this is a temporary workaround for bug 345399
4261           // We need to manually update the scroll buttons disabled state
4262           // if a tab was inserted to the overflow area or removed from it
4263           // without any scrolling and when the tabbar has already
4264           // overflowed.
4265           this.mTabstrip._updateScrollButtonsDisabledState();
4266         ]]></body>
4267       </method>
4269       <method name="_canAdvanceToTab">
4270         <parameter name="aTab"/>
4271         <body>
4272         <![CDATA[
4273           return !aTab.closing;
4274         ]]>
4275         </body>
4276       </method>
4278       <method name="_handleTabTelemetryStart">
4279         <parameter name="aTab"/>
4280         <parameter name="aURI"/>
4281         <body>
4282         <![CDATA[
4283           // Animation-smoothness telemetry/logging
4284           if (Services.telemetry.canRecord || this._tabAnimationLoggingEnabled) {
4285             if (aURI == "about:newtab" && (aTab._tPos == 1 || aTab._tPos == 2)) {
4286               // Indicate newtab page animation where other tabs are unaffected
4287               // (for which case, the 2nd or 3rd tabs are good representatives, even if not absolute)
4288               aTab._recordingTabOpenPlain = true;
4289             }
4290             aTab._recordingHandle = window.QueryInterface(Ci.nsIInterfaceRequestor)
4291                                           .getInterface(Ci.nsIDOMWindowUtils)
4292                                           .startFrameTimeRecording();
4293           }
4295           // Overall animation duration
4296           aTab._animStartTime = Date.now();
4297         ]]>
4298         </body>
4299       </method>
4301       <method name="_handleTabTelemetryEnd">
4302         <parameter name="aTab"/>
4303         <body>
4304         <![CDATA[
4305           if (!aTab._animStartTime) {
4306             return;
4307           }
4309           Services.telemetry.getHistogramById(aTab.closing ?
4310                                               "FX_TAB_ANIM_CLOSE_MS" :
4311                                               "FX_TAB_ANIM_OPEN_MS")
4312                             .add(Date.now() - aTab._animStartTime);
4313           aTab._animStartTime = 0;
4315           // Handle tab animation smoothness telemetry/logging of frame intervals and paint times
4316           if (!("_recordingHandle" in aTab)) {
4317             return;
4318           }
4320           let intervals = window.QueryInterface(Ci.nsIInterfaceRequestor)
4321                                 .getInterface(Ci.nsIDOMWindowUtils)
4322                                 .stopFrameTimeRecording(aTab._recordingHandle);
4323           delete aTab._recordingHandle;
4324           let frameCount = intervals.length;
4326           if (this._tabAnimationLoggingEnabled) {
4327             let msg = "Tab " + (aTab.closing ? "close" : "open") + " (Frame-interval):\n";
4328             for (let i = 0; i < frameCount; i++) {
4329               msg += Math.round(intervals[i]) + "\n";
4330             }
4331             Services.console.logStringMessage(msg);
4332           }
4334           // For telemetry, the first frame interval is not useful since it may represent an interval
4335           // to a relatively old frame (prior to recording start). So we'll ignore it for the average.
4336           if (frameCount > 1) {
4337             let averageInterval = 0;
4338             for (let i = 1; i < frameCount; i++) {
4339               averageInterval += intervals[i];
4340             };
4341             averageInterval = averageInterval / (frameCount - 1);
4343             Services.telemetry.getHistogramById("FX_TAB_ANIM_ANY_FRAME_INTERVAL_MS").add(averageInterval);
4345             if (aTab._recordingTabOpenPlain) {
4346               delete aTab._recordingTabOpenPlain;
4347               // While we do have a telemetry probe NEWTAB_PAGE_ENABLED to monitor newtab preview, it'll be
4348               // easier to overview the data without slicing by it. Hence the additional histograms with _PREVIEW.
4349               let preview = this._browserNewtabpageEnabled ? "_PREVIEW" : "";
4350               Services.telemetry.getHistogramById("FX_TAB_ANIM_OPEN" + preview + "_FRAME_INTERVAL_MS").add(averageInterval);
4351             }
4352           }
4353         ]]>
4354         </body>
4355       </method>
4357       <!-- Deprecated stuff, implemented for backwards compatibility. -->
4358       <property name="mAllTabsPopup" readonly="true"
4359                 onget="return document.getElementById('alltabs-popup');"/>
4360     </implementation>
4362     <handlers>
4363       <handler event="TabSelect" action="this._handleTabSelect();"/>
4365       <handler event="transitionend"><![CDATA[
4366         if (event.propertyName != "max-width")
4367           return;
4369         var tab = event.target;
4371         this._handleTabTelemetryEnd(tab);
4373         if (tab.getAttribute("fadein") == "true") {
4374           if (tab._fullyOpen)
4375             this.adjustTabstrip();
4376           else
4377             this._handleNewTab(tab);
4378         } else if (tab.closing) {
4379           this.tabbrowser._endRemoveTab(tab);
4380         }
4381       ]]></handler>
4383       <handler event="dblclick"><![CDATA[
4384 #ifndef XP_MACOSX
4385         // When the tabbar has an unified appearance with the titlebar
4386         // and menubar, a double-click in it should have the same behavior
4387         // as double-clicking the titlebar
4388         if (TabsInTitlebar.enabled || this.parentNode._dragBindingAlive)
4389           return;
4390 #endif
4392         if (event.button != 0 ||
4393             event.originalTarget.localName != "box")
4394           return;
4396         // See hack note in the tabbrowser-close-tab-button binding
4397         if (!this._blockDblClick)
4398           BrowserOpenTab();
4400         event.preventDefault();
4401       ]]></handler>
4403       <handler event="click" button="0" phase="capturing"><![CDATA[
4404         /* Catches extra clicks meant for the in-tab close button.
4405          * Placed here to avoid leaking (a temporary handler added from the
4406          * in-tab close button binding would close over the tab and leak it
4407          * until the handler itself was removed). (bug 897751)
4408          *
4409          * The only sequence in which a second click event (i.e. dblclik)
4410          * can be dispatched on an in-tab close button is when it is shown
4411          * after the first click (i.e. the first click event was dispatched
4412          * on the tab). This happens when we show the close button only on
4413          * the active tab. (bug 352021)
4414          * The only sequence in which a third click event can be dispatched
4415          * on an in-tab close button is when the tab was opened with a
4416          * double click on the tabbar. (bug 378344)
4417          * In both cases, it is most likely that the close button area has
4418          * been accidentally clicked, therefore we do not close the tab.
4419          *
4420          * We don't want to ignore processing of more than one click event,
4421          * though, since the user might actually be repeatedly clicking to
4422          * close many tabs at once.
4423          */
4424         let target = event.originalTarget;
4425         if (target.classList.contains('tab-close-button')) {
4426           // We preemptively set this to allow the closing-multiple-tabs-
4427           // in-a-row case.
4428           if (this._blockDblClick) {
4429             target._ignoredCloseButtonClicks = true;
4430           } else if (event.detail > 1 && !target._ignoredCloseButtonClicks) {
4431             target._ignoredCloseButtonClicks = true;
4432             event.stopPropagation();
4433             return;
4434           } else {
4435             // Reset the "ignored click" flag
4436             target._ignoredCloseButtonClicks = false;
4437           }
4438         }
4440         /* Protects from close-tab-button errant doubleclick:
4441          * Since we're removing the event target, if the user
4442          * double-clicks the button, the dblclick event will be dispatched
4443          * with the tabbar as its event target (and explicit/originalTarget),
4444          * which treats that as a mouse gesture for opening a new tab.
4445          * In this context, we're manually blocking the dblclick event
4446          * (see tabbrowser-close-tab-button dblclick handler).
4447          */
4448         if (this._blockDblClick) {
4449           if (!("_clickedTabBarOnce" in this)) {
4450             this._clickedTabBarOnce = true;
4451             return;
4452           }
4453           delete this._clickedTabBarOnce;
4454           this._blockDblClick = false;
4455         }
4456       ]]></handler>
4458       <handler event="click"><![CDATA[
4459         if (event.button != 1)
4460           return;
4462         if (event.target.localName == "tab") {
4463           this.tabbrowser.removeTab(event.target, {animate: true, byMouse: true});
4464         } else if (event.originalTarget.localName == "box") {
4465           BrowserOpenTab();
4466         } else {
4467           return;
4468         }
4470         event.stopPropagation();
4471       ]]></handler>
4473       <handler event="keydown" group="system"><![CDATA[
4474         if (event.altKey || event.shiftKey ||
4475 #ifdef XP_MACOSX
4476             !event.metaKey)
4477 #else
4478             !event.ctrlKey || event.metaKey)
4479 #endif
4480           return;
4482         // Don't check if the event was already consumed because tab navigation
4483         // should work always for better user experience.
4485         switch (event.keyCode) {
4486           case KeyEvent.DOM_VK_UP:
4487             this.tabbrowser.moveTabBackward();
4488             break;
4489           case KeyEvent.DOM_VK_DOWN:
4490             this.tabbrowser.moveTabForward();
4491             break;
4492           case KeyEvent.DOM_VK_RIGHT:
4493           case KeyEvent.DOM_VK_LEFT:
4494             this.tabbrowser.moveTabOver(event);
4495             break;
4496           case KeyEvent.DOM_VK_HOME:
4497             this.tabbrowser.moveTabToStart();
4498             break;
4499           case KeyEvent.DOM_VK_END:
4500             this.tabbrowser.moveTabToEnd();
4501             break;
4502           default:
4503             // Consume the keydown event for the above keyboard
4504             // shortcuts only.
4505             return;
4506         }
4507         event.preventDefault();
4508       ]]></handler>
4510       <handler event="dragstart"><![CDATA[
4511         var tab = this._getDragTargetTab(event);
4512         if (!tab || this._isCustomizing)
4513           return;
4515         let dt = event.dataTransfer;
4516         dt.mozSetDataAt(TAB_DROP_TYPE, tab, 0);
4517         let browser = tab.linkedBrowser;
4519         // We must not set text/x-moz-url or text/plain data here,
4520         // otherwise trying to deatch the tab by dropping it on the desktop
4521         // may result in an "internet shortcut"
4522         dt.mozSetDataAt("text/x-moz-text-internal", browser.currentURI.spec, 0);
4524         // Set the cursor to an arrow during tab drags.
4525         dt.mozCursor = "default";
4527         // Create a canvas to which we capture the current tab.
4528         // Until canvas is HiDPI-aware (bug 780362), we need to scale the desired
4529         // canvas size (in CSS pixels) to the window's backing resolution in order
4530         // to get a full-resolution drag image for use on HiDPI displays.
4531         let windowUtils = window.getInterface(Ci.nsIDOMWindowUtils);
4532         let scale = windowUtils.screenPixelsPerCSSPixel / windowUtils.fullZoom;
4533         let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
4534         canvas.mozOpaque = true;
4535         canvas.width = 160 * scale;
4536         canvas.height = 90 * scale;
4537         if (!gMultiProcessBrowser) {
4538           // Bug 863512 - Make page thumbnails work in e10s
4539           PageThumbs.captureToCanvas(browser.contentWindow, canvas);
4540         }
4541         dt.setDragImage(canvas, -16 * scale, -16 * scale);
4543         // _dragData.offsetX/Y give the coordinates that the mouse should be
4544         // positioned relative to the corner of the new window created upon
4545         // dragend such that the mouse appears to have the same position
4546         // relative to the corner of the dragged tab.
4547         function clientX(ele) ele.getBoundingClientRect().left;
4548         let tabOffsetX = clientX(tab) - clientX(this);
4549         tab._dragData = {
4550           offsetX: event.screenX - window.screenX - tabOffsetX,
4551           offsetY: event.screenY - window.screenY,
4552           scrollX: this.mTabstrip.scrollPosition,
4553           screenX: event.screenX
4554         };
4556         event.stopPropagation();
4557       ]]></handler>
4559       <handler event="dragover"><![CDATA[
4560         var effects = this._setEffectAllowedForDataTransfer(event);
4562         var ind = this._tabDropIndicator;
4563         if (effects == "" || effects == "none") {
4564           ind.collapsed = true;
4565           return;
4566         }
4567         event.preventDefault();
4568         event.stopPropagation();
4570         var tabStrip = this.mTabstrip;
4571         var ltr = (window.getComputedStyle(this, null).direction == "ltr");
4573         // autoscroll the tab strip if we drag over the scroll
4574         // buttons, even if we aren't dragging a tab, but then
4575         // return to avoid drawing the drop indicator
4576         var pixelsToScroll = 0;
4577         if (this.getAttribute("overflow") == "true") {
4578           var targetAnonid = event.originalTarget.getAttribute("anonid");
4579           switch (targetAnonid) {
4580             case "scrollbutton-up":
4581               pixelsToScroll = tabStrip.scrollIncrement * -1;
4582               break;
4583             case "scrollbutton-down":
4584               pixelsToScroll = tabStrip.scrollIncrement;
4585               break;
4586           }
4587           if (pixelsToScroll)
4588             tabStrip.scrollByPixels((ltr ? 1 : -1) * pixelsToScroll);
4589         }
4591         if (effects == "move" &&
4592             this == event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0).parentNode) {
4593           ind.collapsed = true;
4594           this._animateTabMove(event);
4595           return;
4596         }
4598         this._finishAnimateTabMove();
4600         if (effects == "link") {
4601           let tab = this._getDragTargetTab(event);
4602           if (tab) {
4603             if (!this._dragTime)
4604               this._dragTime = Date.now();
4605             if (Date.now() >= this._dragTime + this._dragOverDelay)
4606               this.selectedItem = tab;
4607             ind.collapsed = true;
4608             return;
4609           }
4610         }
4612         var rect = tabStrip.getBoundingClientRect();
4613         var newMargin;
4614         if (pixelsToScroll) {
4615           // if we are scrolling, put the drop indicator at the edge
4616           // so that it doesn't jump while scrolling
4617           let scrollRect = tabStrip.scrollClientRect;
4618           let minMargin = scrollRect.left - rect.left;
4619           let maxMargin = Math.min(minMargin + scrollRect.width,
4620                                    scrollRect.right);
4621           if (!ltr)
4622             [minMargin, maxMargin] = [this.clientWidth - maxMargin,
4623                                       this.clientWidth - minMargin];
4624           newMargin = (pixelsToScroll > 0) ? maxMargin : minMargin;
4625         }
4626         else {
4627           let newIndex = this._getDropIndex(event);
4628           if (newIndex == this.childNodes.length) {
4629             let tabRect = this.childNodes[newIndex-1].getBoundingClientRect();
4630             if (ltr)
4631               newMargin = tabRect.right - rect.left;
4632             else
4633               newMargin = rect.right - tabRect.left;
4634           }
4635           else {
4636             let tabRect = this.childNodes[newIndex].getBoundingClientRect();
4637             if (ltr)
4638               newMargin = tabRect.left - rect.left;
4639             else
4640               newMargin = rect.right - tabRect.right;
4641           }
4642         }
4644         ind.collapsed = false;
4646         newMargin += ind.clientWidth / 2;
4647         if (!ltr)
4648           newMargin *= -1;
4650         ind.style.transform = "translate(" + Math.round(newMargin) + "px)";
4651         ind.style.MozMarginStart = (-ind.clientWidth) + "px";
4652       ]]></handler>
4654       <handler event="drop"><![CDATA[
4655         var dt = event.dataTransfer;
4656         var dropEffect = dt.dropEffect;
4657         var draggedTab;
4658         if (dropEffect != "link") { // copy or move
4659           draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
4660           // not our drop then
4661           if (!draggedTab)
4662             return;
4663         }
4665         this._tabDropIndicator.collapsed = true;
4666         event.stopPropagation();
4667         if (draggedTab && dropEffect == "copy") {
4668           // copy the dropped tab (wherever it's from)
4669           let newIndex = this._getDropIndex(event);
4670           let newTab = this.tabbrowser.duplicateTab(draggedTab);
4671           this.tabbrowser.moveTabTo(newTab, newIndex);
4672           if (draggedTab.parentNode != this || event.shiftKey)
4673             this.selectedItem = newTab;
4674         } else if (draggedTab && draggedTab.parentNode == this) {
4675           this._finishAnimateTabMove();
4677           // actually move the dragged tab
4678           if ("animDropIndex" in draggedTab._dragData) {
4679             let newIndex = draggedTab._dragData.animDropIndex;
4680             if (newIndex > draggedTab._tPos)
4681               newIndex--;
4682             this.tabbrowser.moveTabTo(draggedTab, newIndex);
4683           }
4684         } else if (draggedTab) {
4685           // swap the dropped tab with a new one we create and then close
4686           // it in the other window (making it seem to have moved between
4687           // windows)
4688           let newIndex = this._getDropIndex(event);
4689           let newTab = this.tabbrowser.addTab("about:blank");
4690           let newBrowser = this.tabbrowser.getBrowserForTab(newTab);
4691           // Stop the about:blank load
4692           newBrowser.stop();
4693           // make sure it has a docshell
4694           newBrowser.docShell;
4696           let numPinned = this.tabbrowser._numPinnedTabs;
4697           if (newIndex < numPinned || draggedTab.pinned && newIndex == numPinned)
4698             this.tabbrowser.pinTab(newTab);
4699           this.tabbrowser.moveTabTo(newTab, newIndex);
4701           // We need to select the tab before calling swapBrowsersAndCloseOther
4702           // so that window.content in chrome windows points to the right tab
4703           // when pagehide/show events are fired.
4704           this.tabbrowser.selectedTab = newTab;
4706           draggedTab.parentNode._finishAnimateTabMove();
4707           this.tabbrowser.swapBrowsersAndCloseOther(newTab, draggedTab);
4709           // Call updateCurrentBrowser to make sure the URL bar is up to date
4710           // for our new tab after we've done swapBrowsersAndCloseOther.
4711           this.tabbrowser.updateCurrentBrowser(true);
4712         } else {
4713           // Pass true to disallow dropping javascript: or data: urls
4714           let url;
4715           try {
4716             url = browserDragAndDrop.drop(event, { }, true);
4717           } catch (ex) {}
4719           if (!url)
4720             return;
4722           let bgLoad = Services.prefs.getBoolPref("browser.tabs.loadInBackground");
4724           if (event.shiftKey)
4725             bgLoad = !bgLoad;
4727           let tab = this._getDragTargetTab(event);
4728           if (!tab || dropEffect == "copy") {
4729             // We're adding a new tab.
4730             let newIndex = this._getDropIndex(event);
4731             let newTab = this.tabbrowser.loadOneTab(url, {inBackground: bgLoad, allowThirdPartyFixup: true});
4732             this.tabbrowser.moveTabTo(newTab, newIndex);
4733           } else {
4734             // Load in an existing tab.
4735             try {
4736               let webNav = Ci.nsIWebNavigation;
4737               let flags = webNav.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP |
4738                           webNav.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
4739               this.tabbrowser.getBrowserForTab(tab).loadURIWithFlags(url, flags);
4740               if (!bgLoad)
4741                 this.selectedItem = tab;
4742             } catch(ex) {
4743               // Just ignore invalid urls
4744             }
4745           }
4746         }
4748         if (draggedTab) {
4749           delete draggedTab._dragData;
4750         }
4751       ]]></handler>
4753       <handler event="dragend"><![CDATA[
4754         // Note: while this case is correctly handled here, this event
4755         // isn't dispatched when the tab is moved within the tabstrip,
4756         // see bug 460801.
4758         this._finishAnimateTabMove();
4760         var dt = event.dataTransfer;
4761         var draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
4762         if (dt.mozUserCancelled || dt.dropEffect != "none" || this._isCustomizing) {
4763           delete draggedTab._dragData;
4764           return;
4765         }
4767         // Disable detach within the browser toolbox
4768         var eX = event.screenX;
4769         var eY = event.screenY;
4770         var wX = window.screenX;
4771         // check if the drop point is horizontally within the window
4772         if (eX > wX && eX < (wX + window.outerWidth)) {
4773           let bo = this.mTabstrip.boxObject;
4774           // also avoid detaching if the the tab was dropped too close to
4775           // the tabbar (half a tab)
4776           let endScreenY = bo.screenY + 1.5 * bo.height;
4777           if (eY < endScreenY && eY > window.screenY)
4778             return;
4779         }
4781         // screen.availLeft et. al. only check the screen that this window is on,
4782         // but we want to look at the screen the tab is being dropped onto.
4783         var sX = {}, sY = {}, sWidth = {}, sHeight = {};
4784         Cc["@mozilla.org/gfx/screenmanager;1"]
4785           .getService(Ci.nsIScreenManager)
4786           .screenForRect(eX, eY, 1, 1)
4787           .GetAvailRect(sX, sY, sWidth, sHeight);
4788         // ensure new window entirely within screen
4789         var winWidth = Math.min(window.outerWidth, sWidth.value);
4790         var winHeight = Math.min(window.outerHeight, sHeight.value);
4791         var left = Math.min(Math.max(eX - draggedTab._dragData.offsetX, sX.value),
4792                             sX.value + sWidth.value - winWidth);
4793         var top = Math.min(Math.max(eY - draggedTab._dragData.offsetY, sY.value),
4794                            sY.value + sHeight.value - winHeight);
4796         delete draggedTab._dragData;
4798         if (this.tabbrowser.tabs.length == 1) {
4799           // resize _before_ move to ensure the window fits the new screen.  if
4800           // the window is too large for its screen, the window manager may do
4801           // automatic repositioning.
4802           window.resizeTo(winWidth, winHeight);
4803           window.moveTo(left, top);
4804           window.focus();
4805         } else {
4806           this.tabbrowser.replaceTabWithWindow(draggedTab, { screenX: left,
4807                                                              screenY: top,
4808 #ifndef XP_WIN
4809                                                              outerWidth: winWidth,
4810                                                              outerHeight: winHeight
4811 #endif
4812                                                              });
4813         }
4814         event.stopPropagation();
4815       ]]></handler>
4817       <handler event="dragexit"><![CDATA[
4818         this._dragTime = 0;
4820         // This does not work at all (see bug 458613)
4821         var target = event.relatedTarget;
4822         while (target && target != this)
4823           target = target.parentNode;
4824         if (target)
4825           return;
4827         this._tabDropIndicator.collapsed = true;
4828         event.stopPropagation();
4829       ]]></handler>
4830     </handlers>
4831   </binding>
4833   <!-- close-tab-button binding
4834        This binding relies on the structure of the tabbrowser binding.
4835        Therefore it should only be used as a child of the tab or the tabs
4836        element (in both cases, when they are anonymous nodes of <tabbrowser>).
4837   -->
4838   <binding id="tabbrowser-close-tab-button"
4839            extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-image">
4840     <handlers>
4841       <handler event="click" button="0"><![CDATA[
4842         var bindingParent = document.getBindingParent(this);
4843         var tabContainer = bindingParent.parentNode;
4844         tabContainer.tabbrowser.removeTab(bindingParent, {animate: true, byMouse: true});
4845         // This enables double-click protection for the tab container
4846         // (see tabbrowser-tabs 'click' handler).
4847         tabContainer._blockDblClick = true;
4848       ]]></handler>
4850       <handler event="dblclick" button="0" phase="capturing">
4851         // for the one-close-button case
4852         event.stopPropagation();
4853       </handler>
4855       <handler event="dragstart">
4856         event.stopPropagation();
4857       </handler>
4858     </handlers>
4859   </binding>
4861   <binding id="tabbrowser-tab" display="xul:hbox"
4862            extends="chrome://global/content/bindings/tabbox.xml#tab">
4863     <resources>
4864       <stylesheet src="chrome://browser/content/tabbrowser.css"/>
4865     </resources>
4867     <content context="tabContextMenu" closetabtext="&closeTab.label;">
4868       <xul:stack class="tab-stack" flex="1">
4869         <xul:hbox xbl:inherits="pinned,selected,titlechanged,fadein"
4870                   class="tab-background">
4871           <xul:hbox xbl:inherits="pinned,selected,titlechanged"
4872                     class="tab-background-start"/>
4873           <xul:hbox xbl:inherits="pinned,selected,titlechanged"
4874                     class="tab-background-middle"/>
4875           <xul:hbox xbl:inherits="pinned,selected,titlechanged"
4876                     class="tab-background-end"/>
4877         </xul:hbox>
4878         <xul:hbox xbl:inherits="pinned,selected,titlechanged"
4879                   class="tab-content" align="center">
4880           <xul:image xbl:inherits="fadein,pinned,busy,progress,selected"
4881                      class="tab-throbber"
4882                      role="presentation"
4883                      layer="true" />
4884           <xul:image xbl:inherits="src=image,fadein,pinned,selected"
4885                      anonid="tab-icon-image"
4886                      class="tab-icon-image"
4887                      validate="never"
4888                      role="presentation"/>
4889           <xul:label flex="1"
4890                      anonid="tab-label"
4891                      xbl:inherits="value=visibleLabel,crop,accesskey,fadein,pinned,selected"
4892                      class="tab-text tab-label"
4893                      role="presentation"/>
4894           <xul:toolbarbutton anonid="close-button"
4895                              xbl:inherits="fadein,pinned,selected"
4896                              class="tab-close-button close-icon"/>
4897         </xul:hbox>
4898       </xul:stack>
4899     </content>
4901     <implementation>
4902       <property name="label">
4903         <getter>
4904           return this.getAttribute("label");
4905         </getter>
4906         <setter>
4907           this.setAttribute("label", val);
4908           let event = new CustomEvent("TabLabelModified", {
4909             bubbles: true,
4910             cancelable: true
4911           });
4912           this.dispatchEvent(event);
4914           // Let listeners prevent synchronizing the actual label to the
4915           // visible label (allowing them to override the visible label).
4916           if (!event.defaultPrevented)
4917             this.visibleLabel = val;
4918         </setter>
4919       </property>
4920       <property name="visibleLabel">
4921         <getter>
4922           return this.getAttribute("visibleLabel");
4923         </getter>
4924         <setter>
4925           this.setAttribute("visibleLabel", val);
4926         </setter>
4927       </property>
4928       <property name="pinned" readonly="true">
4929         <getter>
4930           return this.getAttribute("pinned") == "true";
4931         </getter>
4932       </property>
4933       <property name="hidden" readonly="true">
4934         <getter>
4935           return this.getAttribute("hidden") == "true";
4936         </getter>
4937       </property>
4939       <property name="lastAccessed">
4940         <getter>
4941           return this.selected ? Date.now() : this._lastAccessed;
4942         </getter>
4943         <setter>
4944           this._lastAccessed = val;
4945         </setter>
4946       </property>
4947       <field name="_lastAccessed">0</field>
4949       <field name="mOverCloseButton">false</field>
4950       <field name="mCorrespondingMenuitem">null</field>
4951       <field name="closing">false</field>
4953       <method name="_mouseenter">
4954         <body><![CDATA[
4955           if (this.hidden || this.closing)
4956             return;
4958           let tabContainer = this.parentNode;
4959           let visibleTabs = tabContainer.tabbrowser.visibleTabs;
4960           let tabIndex = visibleTabs.indexOf(this);
4961           if (tabIndex == 0) {
4962             tabContainer._beforeHoveredTab = null;
4963           } else {
4964             let candidate = visibleTabs[tabIndex - 1];
4965             if (!candidate.selected) {
4966               tabContainer._beforeHoveredTab = candidate;
4967               candidate.setAttribute("beforehovered", "true");
4968             }
4969           }
4971           if (tabIndex == visibleTabs.length - 1) {
4972             tabContainer._afterHoveredTab = null;
4973           } else {
4974             let candidate = visibleTabs[tabIndex + 1];
4975             if (!candidate.selected) {
4976               tabContainer._afterHoveredTab = candidate;
4977               candidate.setAttribute("afterhovered", "true");
4978             }
4979           }
4981           tabContainer._hoveredTab = this;
4982         ]]></body>
4983       </method>
4985       <method name="_mouseleave">
4986         <body><![CDATA[
4987           let tabContainer = this.parentNode;
4988           if (tabContainer._beforeHoveredTab) {
4989             tabContainer._beforeHoveredTab.removeAttribute("beforehovered");
4990             tabContainer._beforeHoveredTab = null;
4991           }
4992           if (tabContainer._afterHoveredTab) {
4993             tabContainer._afterHoveredTab.removeAttribute("afterhovered");
4994             tabContainer._afterHoveredTab = null;
4995           }
4997           tabContainer._hoveredTab = null;
4998         ]]></body>
4999       </method>
5000     </implementation>
5002     <handlers>
5003       <handler event="mouseover"><![CDATA[
5004         let anonid = event.originalTarget.getAttribute("anonid");
5005         if (anonid == "close-button")
5006           this.mOverCloseButton = true;
5008         this._mouseenter();
5009       ]]></handler>
5010       <handler event="mouseout"><![CDATA[
5011         let anonid = event.originalTarget.getAttribute("anonid");
5012         if (anonid == "close-button")
5013           this.mOverCloseButton = false;
5015         this._mouseleave();
5016       ]]></handler>
5017       <handler event="dragstart" phase="capturing">
5018         this.style.MozUserFocus = '';
5019       </handler>
5020       <handler event="mousedown" phase="capturing">
5021       <![CDATA[
5022         if (this.selected) {
5023           this.style.MozUserFocus = 'ignore';
5024           this.clientTop; // just using this to flush style updates
5025         } else if (this.mOverCloseButton) {
5026           // Prevent tabbox.xml from selecting the tab.
5027           event.stopPropagation();
5028         }
5029       ]]>
5030       </handler>
5031       <handler event="mouseup">
5032         this.style.MozUserFocus = '';
5033       </handler>
5034     </handlers>
5035   </binding>
5037   <binding id="tabbrowser-alltabs-popup"
5038            extends="chrome://global/content/bindings/popup.xml#popup">
5039     <implementation implements="nsIDOMEventListener">
5040       <method name="_tabOnAttrModified">
5041         <parameter name="aEvent"/>
5042         <body><![CDATA[
5043           var tab = aEvent.target;
5044           if (tab.mCorrespondingMenuitem)
5045             this._setMenuitemAttributes(tab.mCorrespondingMenuitem, tab);
5046         ]]></body>
5047       </method>
5049       <method name="_tabOnTabClose">
5050         <parameter name="aEvent"/>
5051         <body><![CDATA[
5052           var tab = aEvent.target;
5053           if (tab.mCorrespondingMenuitem)
5054             this.removeChild(tab.mCorrespondingMenuitem);
5055         ]]></body>
5056       </method>
5058       <method name="handleEvent">
5059         <parameter name="aEvent"/>
5060         <body><![CDATA[
5061           switch (aEvent.type) {
5062             case "TabAttrModified":
5063               this._tabOnAttrModified(aEvent);
5064               break;
5065             case "TabClose":
5066               this._tabOnTabClose(aEvent);
5067               break;
5068             case "scroll":
5069               this._updateTabsVisibilityStatus();
5070               break;
5071           }
5072         ]]></body>
5073       </method>
5075       <method name="_updateTabsVisibilityStatus">
5076         <body><![CDATA[
5077           var tabContainer = gBrowser.tabContainer;
5078           // We don't want menu item decoration unless there is overflow.
5079           if (tabContainer.getAttribute("overflow") != "true")
5080             return;
5082           var tabstripBO = tabContainer.mTabstrip.scrollBoxObject;
5083           for (var i = 0; i < this.childNodes.length; i++) {
5084             let curTab = this.childNodes[i].tab;
5085             if (!curTab) // "Tab Groups" menuitem and its menuseparator
5086               continue;
5087             let curTabBO = curTab.boxObject;
5088             if (curTabBO.screenX >= tabstripBO.screenX &&
5089                 curTabBO.screenX + curTabBO.width <= tabstripBO.screenX + tabstripBO.width)
5090               this.childNodes[i].setAttribute("tabIsVisible", "true");
5091             else
5092               this.childNodes[i].removeAttribute("tabIsVisible");
5093           }
5094         ]]></body>
5095       </method>
5097       <method name="_createTabMenuItem">
5098         <parameter name="aTab"/>
5099         <body><![CDATA[
5100           var menuItem = document.createElementNS(
5101             "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
5102             "menuitem");
5104           menuItem.setAttribute("class", "menuitem-iconic alltabs-item menuitem-with-favicon");
5106           this._setMenuitemAttributes(menuItem, aTab);
5108           aTab.mCorrespondingMenuitem = menuItem;
5109           menuItem.tab = aTab;
5111           this.appendChild(menuItem);
5112         ]]></body>
5113       </method>
5115       <method name="_setMenuitemAttributes">
5116         <parameter name="aMenuitem"/>
5117         <parameter name="aTab"/>
5118         <body><![CDATA[
5119           aMenuitem.setAttribute("label", aTab.label);
5120           aMenuitem.setAttribute("crop", aTab.getAttribute("crop"));
5122           if (aTab.hasAttribute("busy")) {
5123             aMenuitem.setAttribute("busy", aTab.getAttribute("busy"));
5124             aMenuitem.removeAttribute("image");
5125           } else {
5126             aMenuitem.setAttribute("image", aTab.getAttribute("image"));
5127             aMenuitem.removeAttribute("busy");
5128           }
5130           if (aTab.hasAttribute("pending"))
5131             aMenuitem.setAttribute("pending", aTab.getAttribute("pending"));
5132           else
5133             aMenuitem.removeAttribute("pending");
5135           if (aTab.selected)
5136             aMenuitem.setAttribute("selected", "true");
5137           else
5138             aMenuitem.removeAttribute("selected");
5139         ]]></body>
5140       </method>
5141     </implementation>
5143     <handlers>
5144       <handler event="popupshowing">
5145       <![CDATA[
5146         document.getElementById("alltabs_undoCloseTab").disabled =
5147           SessionStore.getClosedTabCount(window) == 0;
5149         var tabcontainer = gBrowser.tabContainer;
5151         // Listen for changes in the tab bar.
5152         tabcontainer.addEventListener("TabAttrModified", this, false);
5153         tabcontainer.addEventListener("TabClose", this, false);
5154         tabcontainer.mTabstrip.addEventListener("scroll", this, false);
5156         let tabs = gBrowser.visibleTabs;
5157         for (var i = 0; i < tabs.length; i++) {
5158           if (!tabs[i].pinned)
5159             this._createTabMenuItem(tabs[i]);
5160         }
5161         this._updateTabsVisibilityStatus();
5162       ]]></handler>
5164       <handler event="popuphidden">
5165       <![CDATA[
5166         // clear out the menu popup and remove the listeners
5167         for (let i = this.childNodes.length - 1; i > 0; i--) {
5168           let menuItem = this.childNodes[i];
5169           if (menuItem.tab) {
5170             menuItem.tab.mCorrespondingMenuitem = null;
5171             this.removeChild(menuItem);
5172           }
5173         }
5174         var tabcontainer = gBrowser.tabContainer;
5175         tabcontainer.mTabstrip.removeEventListener("scroll", this, false);
5176         tabcontainer.removeEventListener("TabAttrModified", this, false);
5177         tabcontainer.removeEventListener("TabClose", this, false);
5178       ]]></handler>
5180       <handler event="DOMMenuItemActive">
5181       <![CDATA[
5182         var tab = event.target.tab;
5183         if (tab) {
5184           let overLink = tab.linkedBrowser.currentURI.spec;
5185           if (overLink == "about:blank")
5186             overLink = "";
5187           XULBrowserWindow.setOverLink(overLink, null);
5188         }
5189       ]]></handler>
5191       <handler event="DOMMenuItemInactive">
5192       <![CDATA[
5193         XULBrowserWindow.setOverLink("", null);
5194       ]]></handler>
5196       <handler event="command"><![CDATA[
5197         if (event.target.tab)
5198           gBrowser.selectedTab = event.target.tab;
5199       ]]></handler>
5201     </handlers>
5202   </binding>
5204   <binding id="statuspanel" display="xul:hbox">
5205     <content>
5206       <xul:hbox class="statuspanel-inner">
5207         <xul:label class="statuspanel-label"
5208                    role="status"
5209                    aria-live="off"
5210                    xbl:inherits="value=label,crop,mirror"
5211                    flex="1"
5212                    crop="end"/>
5213       </xul:hbox>
5214     </content>
5216     <implementation implements="nsIDOMEventListener">
5217       <constructor><![CDATA[
5218         window.addEventListener("resize", this, false);
5219       ]]></constructor>
5221       <destructor><![CDATA[
5222         window.removeEventListener("resize", this, false);
5223         MousePosTracker.removeListener(this);
5224       ]]></destructor>
5226       <property name="label">
5227         <setter><![CDATA[
5228           if (!this.label) {
5229             this.removeAttribute("mirror");
5230             this.removeAttribute("sizelimit");
5231           }
5233           this.style.minWidth = this.getAttribute("type") == "status" &&
5234                                 this.getAttribute("previoustype") == "status"
5235                                   ? getComputedStyle(this).width : "";
5237           if (val) {
5238             this.setAttribute("label", val);
5239             this.removeAttribute("inactive");
5240             this._calcMouseTargetRect();
5241             MousePosTracker.addListener(this);
5242           } else {
5243             this.setAttribute("inactive", "true");
5244             MousePosTracker.removeListener(this);
5245           }
5247           return val;
5248         ]]></setter>
5249         <getter>
5250           return this.hasAttribute("inactive") ? "" : this.getAttribute("label");
5251         </getter>
5252       </property>
5254       <method name="getMouseTargetRect">
5255         <body><![CDATA[
5256           return this._mouseTargetRect;
5257         ]]></body>
5258       </method>
5260       <method name="onMouseEnter">
5261         <body>
5262           this._mirror();
5263         </body>
5264       </method>
5266       <method name="onMouseLeave">
5267         <body>
5268           this._mirror();
5269         </body>
5270       </method>
5272       <method name="handleEvent">
5273         <parameter name="event"/>
5274         <body><![CDATA[
5275           if (!this.label)
5276             return;
5278           switch (event.type) {
5279             case "resize":
5280               this._calcMouseTargetRect();
5281               break;
5282           }
5283         ]]></body>
5284       </method>
5286       <method name="_calcMouseTargetRect">
5287         <body><![CDATA[
5288           let container = this.parentNode;
5289           let alignRight = (getComputedStyle(container).direction == "rtl");
5290           let panelRect = this.getBoundingClientRect();
5291           let containerRect = container.getBoundingClientRect();
5293           this._mouseTargetRect = {
5294             top:    panelRect.top,
5295             bottom: panelRect.bottom,
5296             left:   alignRight ? containerRect.right - panelRect.width : containerRect.left,
5297             right:  alignRight ? containerRect.right : containerRect.left + panelRect.width
5298           };
5299         ]]></body>
5300       </method>
5302       <method name="_mirror">
5303         <body>
5304           if (this.hasAttribute("mirror"))
5305             this.removeAttribute("mirror");
5306           else
5307             this.setAttribute("mirror", "true");
5309           if (!this.hasAttribute("sizelimit")) {
5310             this.setAttribute("sizelimit", "true");
5311             this._calcMouseTargetRect();
5312           }
5313         </body>
5314       </method>
5315     </implementation>
5316   </binding>
5318   <binding id="tabbrowser-tabpanels"
5319            extends="chrome://global/content/bindings/tabbox.xml#tabpanels">
5320     <implementation>
5321       <field name="_selectedIndex">0</field>
5323       <property name="selectedIndex">
5324         <getter>
5325         <![CDATA[
5326           return this._selectedIndex;
5327         ]]>
5328         </getter>
5330         <setter>
5331         <![CDATA[
5332           if (val < 0 || val >= this.childNodes.length)
5333             return val;
5335           let toTab = this.getRelatedElement(this.childNodes[val]);
5336           let fromTab = this._selectedPanel ? this.getRelatedElement(this._selectedPanel)
5337                                             : null;
5339           let switchPromise = gBrowser._prepareForTabSwitch(toTab, fromTab);
5341           var panel = this._selectedPanel;
5342           this._selectedPanel = this.childNodes[val];
5343           if (this._selectedPanel != panel) {
5344             var event = document.createEvent("Events");
5345             event.initEvent("select", true, true);
5346             this.dispatchEvent(event);
5347           }
5349           this._selectedIndex = val;
5351           switchPromise.then(() => {
5352             this.setAttribute("selectedIndex", val);
5353             gBrowser._finalizeTabSwitch(toTab, fromTab);
5354           }, () => {
5355             // If the promise rejected, that means we don't want to actually
5356             // flip the deck, so we cancel the tab switch.
5357             gBrowser._cancelTabSwitch(toTab);
5358           });
5360           return val;
5361         ]]>
5362         </setter>
5363       </property>
5364     </implementation>
5365   </binding>
5367 </bindings>