3 <!-- ***** BEGIN LICENSE BLOCK *****
4 - Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 - The contents of this file are subject to the Mozilla Public License Version
7 - 1.1 (the "License"); you may not use this file except in compliance with
8 - the License. You may obtain a copy of the License at
9 - http://www.mozilla.org/MPL/
11 - Software distributed under the License is distributed on an "AS IS" basis,
12 - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 - for the specific language governing rights and limitations under the
16 - The Original Code is this file as it was released on March 28, 2001.
18 - The Initial Developer of the Original Code is
20 - Portions created by the Initial Developer are Copyright (C) 2001
21 - the Initial Developer. All Rights Reserved.
24 - David Hyatt <hyatt@netscape.com> (Original Author of <tabbrowser>)
25 - Mike Connor <mconnor@steelgryphon.com>
26 - Peter Parente <parente@cs.unc.edu>
27 - Giorgio Maone <g.maone@informaction.com>
28 - Asaf Romano <mozilla.mano@sent.com>
29 - Seth Spitzer <sspitzer@mozilla.org>
30 - Simon Bünzli <zeniko@gmail.com>
31 - Michael Ventnor <ventnor.bugzilla@yahoo.com.au>
32 - Mark Pilgrim <pilgrim@gmail.com>
33 - Dão Gottwald <dao@mozilla.com>
34 - Paul O’Shannessy <paul@oshannessy.com>
35 - Rob Arnold <tellrob@gmail.com>
37 - Alternatively, the contents of this file may be used under the terms of
38 - either the GNU General Public License Version 2 or later (the "GPL"), or
39 - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
40 - in which case the provisions of the GPL or the LGPL are applicable instead
41 - of those above. If you wish to allow use of your version of this file only
42 - under the terms of either the GPL or the LGPL, and not to allow others to
43 - use your version of this file under the terms of the MPL, indicate your
44 - decision by deleting the provisions above and replace them with the notice
45 - and other provisions required by the GPL or the LGPL. If you do not delete
46 - the provisions above, a recipient may use your version of this file under
47 - the terms of any one of the MPL, the GPL or the LGPL.
49 - ***** END LICENSE BLOCK ***** -->
52 <!ENTITY % tabBrowserDTD SYSTEM "chrome://browser/locale/tabbrowser.dtd" >
56 <bindings id="tabBrowserBindings"
57 xmlns="http://www.mozilla.org/xbl"
58 xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
59 xmlns:xbl="http://www.mozilla.org/xbl">
61 <binding id="tabbrowser">
63 <stylesheet src="chrome://browser/content/tabbrowser.css"/>
67 <xul:stringbundle anonid="tbstringbundle" src="chrome://browser/locale/tabbrowser.properties"/>
68 <xul:tabbox anonid="tabbox" class="tabbrowser-tabbox"
69 flex="1" eventnode="document" xbl:inherits="handleCtrlPageUpDown"
70 onselect="if (event.target.localName == 'tabpanels') this.parentNode.updateCurrentBrowser();">
71 <xul:tabpanels flex="1" class="plain" selectedIndex="0" anonid="panelcontainer">
72 <xul:notificationbox flex="1">
73 <xul:stack flex="1" anonid="browserStack">
74 <xul:browser type="content-primary" message="true" disablehistory="true"
75 xbl:inherits="tooltip=contenttooltip,contextmenu=contentcontextmenu,autocompletepopup"/>
77 </xul:notificationbox>
82 <implementation implements="nsIDOMEventListener">
84 <property name="tabContextMenu" readonly="true"
85 onget="return this.tabContainer.contextMenu;"/>
87 <field name="tabContainer" readonly="true">
88 document.getElementById(this.getAttribute("tabcontainer"));
90 <field name="tabs" readonly="true">
91 this.tabContainer.childNodes;
93 <property name="visibleTabs" readonly="true">
95 return Array.filter(this.tabs, function(tab) {
96 return !tab.hidden && this._removingTabs.indexOf(tab) == -1;
100 <field name="mURIFixup" readonly="true">
101 Components.classes["@mozilla.org/docshell/urifixup;1"]
102 .getService(Components.interfaces.nsIURIFixup);
104 <field name="mFaviconService" readonly="true">
105 Components.classes["@mozilla.org/browser/favicon-service;1"]
106 .getService(Components.interfaces.nsIFaviconService);
108 <field name="_placesAutocomplete" readonly="true">
109 Components.classes["@mozilla.org/autocomplete/search;1?name=history"]
110 .getService(Components.interfaces.mozIPlacesAutoComplete);
112 <field name="mTabBox" readonly="true">
113 document.getAnonymousElementByAttribute(this, "anonid", "tabbox");
115 <field name="mPanelContainer" readonly="true">
116 document.getAnonymousElementByAttribute(this, "anonid", "panelcontainer");
118 <field name="mStringBundle">
119 document.getAnonymousElementByAttribute(this, "anonid", "tbstringbundle");
121 <field name="mCurrentTab">
124 <field name="_lastRelatedTab">
127 <field name="mCurrentBrowser">
130 <field name="mProgressListeners">
133 <field name="mTabsProgressListeners">
136 <field name="mTabListeners">
139 <field name="mTabFilters">
142 <field name="mTabbedMode">
145 <field name="mIsBusy">
148 <field name="arrowKeysShouldWrap" readonly="true">
155 <field name="mAddProgressListenerWasCalled">
158 <field name="_browsers">
162 <field name="_autoScrollPopup">
166 <field name="_previewMode">
170 <property name="_numPinnedTabs" readonly="true">
172 for (var i = 0; i < this.tabs.length; i++) {
173 if (!this.tabs[i].pinned)
180 <method name="pinTab">
181 <parameter name="aTab"/>
189 this.moveTabTo(aTab, this._numPinnedTabs);
190 aTab.setAttribute("pinned", "true");
191 this.tabContainer._positionPinnedTabs();
192 this.tabContainer.adjustTabstrip();
194 this.getBrowserForTab(aTab).docShell.isAppTab = true;
196 let event = document.createEvent("Events");
197 event.initEvent("TabPinned", true, false);
198 aTab.dispatchEvent(event);
202 <method name="unpinTab">
203 <parameter name="aTab"/>
208 this.moveTabTo(aTab, this._numPinnedTabs - 1);
209 aTab.setAttribute("fadein", "true");
210 aTab.removeAttribute("pinned");
211 aTab.style.MozMarginStart = "";
212 this.tabContainer._positionPinnedTabs();
213 this.tabContainer.adjustTabstrip();
215 this.getBrowserForTab(aTab).docShell.isAppTab = false;
217 let event = document.createEvent("Events");
218 event.initEvent("TabUnpinned", true, false);
219 aTab.dispatchEvent(event);
223 <method name="previewTab">
224 <parameter name="aTab"/>
225 <parameter name="aCallback"/>
228 let currentTab = this.selectedTab;
230 // Suppress focus, ownership and selected tab changes
231 this._previewMode = true;
232 this.selectedTab = aTab;
235 this.selectedTab = currentTab;
236 this._previewMode = false;
242 <method name="getBrowserAtIndex">
243 <parameter name="aIndex"/>
246 return this.browsers[aIndex];
251 <method name="getBrowserIndexForDocument">
252 <parameter name="aDocument"/>
255 var tab = this._getTabForContentWindow(aDocument.defaultView);
256 return tab ? tab._tPos : -1;
261 <method name="getBrowserForDocument">
262 <parameter name="aDocument"/>
265 var tab = this._getTabForContentWindow(aDocument.defaultView);
266 return tab ? tab.linkedBrowser : null;
271 <method name="_getTabForContentWindow">
272 <parameter name="aWindow"/>
275 for (let i = 0; i < this.browsers.length; i++) {
276 if (this.browsers[i].contentWindow == aWindow)
284 <method name="getNotificationBox">
285 <parameter name="aBrowser"/>
288 return (aBrowser || this.mCurrentBrowser).parentNode.parentNode;
293 <method name="getTabModalPromptBox">
294 <parameter name="aBrowser"/>
297 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
298 let browser = (aBrowser || this.mCurrentBrowser);
299 let stack = browser.parentNode;
303 appendPrompt : function(args, onCloseCallback) {
304 let count = browser.getAttribute("tabmodalPromptShowing");
306 count = parseInt(count) + 1;
309 browser.setAttribute("tabmodalPromptShowing", count);
311 let newPrompt = document.createElementNS(XUL_NS, "tabmodalprompt");
312 stack.appendChild(newPrompt);
313 newPrompt.clientTop; // style flush to assure binding is attached
315 let tab = self._getTabForContentWindow(browser.contentWindow);
316 newPrompt.init(args, tab, onCloseCallback);
320 removePrompt : function(aPrompt) {
321 let count = parseInt(browser.getAttribute("tabmodalPromptShowing"));
324 browser.setAttribute("tabmodalPromptShowing", count);
326 browser.removeAttribute("tabmodalPromptShowing");
327 stack.removeChild(aPrompt);
330 listPrompts : function(aPrompt) {
332 let els = stack.getElementsByTagNameNS(XUL_NS, "tabmodalprompt");
333 // NodeList --> real JS array
334 for (let i = 0; i < els.length; i++)
335 prompts.push(els[i]);
345 <method name="_callProgressListeners">
346 <parameter name="aBrowser"/>
347 <parameter name="aMethod"/>
348 <parameter name="aArguments"/>
349 <parameter name="aCallGlobalListeners"/>
350 <parameter name="aCallTabsListeners"/>
355 aBrowser = this.mCurrentBrowser;
357 if (aCallGlobalListeners != false &&
358 aBrowser == this.mCurrentBrowser) {
359 this.mProgressListeners.forEach(function (p) {
362 if (!p[aMethod].apply(p, aArguments))
365 // don't inhibit other listeners
366 Components.utils.reportError(e);
372 if (aCallTabsListeners != false) {
373 aArguments.unshift(aBrowser);
375 this.mTabsProgressListeners.forEach(function (p) {
378 if (!p[aMethod].apply(p, aArguments))
381 // don't inhibit other listeners
382 Components.utils.reportError(e);
392 <!-- A web progress listener object definition for a given tab. -->
393 <method name="mTabProgressListener">
394 <parameter name="aTab"/>
395 <parameter name="aBrowser"/>
396 <parameter name="aStartsBlank"/>
403 mBlank: aStartsBlank,
405 // cache flags for correct status UI update after tab switching
411 // count of open requests (should always be 0 or 1)
414 destroy: function () {
416 delete this.mBrowser;
417 delete this.mTabBrowser;
420 _callProgressListeners: function () {
421 Array.unshift(arguments, this.mBrowser);
422 return this.mTabBrowser._callProgressListeners.apply(this.mTabBrowser, arguments);
425 onProgressChange: function (aWebProgress, aRequest,
426 aCurSelfProgress, aMaxSelfProgress,
427 aCurTotalProgress, aMaxTotalProgress) {
428 this.mTotalProgress = aMaxTotalProgress ? aCurTotalProgress / aMaxTotalProgress : 0;
433 if (this.mTotalProgress)
434 this.mTab.setAttribute("progress", "true");
436 this._callProgressListeners("onProgressChange",
437 [aWebProgress, aRequest,
438 aCurSelfProgress, aMaxSelfProgress,
439 aCurTotalProgress, aMaxTotalProgress]);
442 onProgressChange64: function (aWebProgress, aRequest,
443 aCurSelfProgress, aMaxSelfProgress,
444 aCurTotalProgress, aMaxTotalProgress) {
445 return this.onProgressChange(aWebProgress, aRequest,
446 aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress,
450 onStateChange: function (aWebProgress, aRequest, aStateFlags, aStatus) {
454 var oldBlank = this.mBlank;
456 const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
457 const nsIChannel = Components.interfaces.nsIChannel;
459 if (aStateFlags & nsIWebProgressListener.STATE_START) {
460 this.mRequestCount++;
462 else if (aStateFlags & nsIWebProgressListener.STATE_STOP) {
463 const NS_ERROR_UNKNOWN_HOST = 2152398878;
464 if (--this.mRequestCount > 0 && aStatus == NS_ERROR_UNKNOWN_HOST) {
465 // to prevent bug 235825: wait for the request handled
466 // by the automatic keyword resolver
469 // since we (try to) only handle STATE_STOP of the last request,
470 // the count of open requests should now be 0
471 this.mRequestCount = 0;
474 if (aStateFlags & nsIWebProgressListener.STATE_START &&
475 aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
476 // It's okay to clear what the user typed when we start
477 // loading a document. If the user types, this counter gets
478 // set to zero, if the document load ends without an
479 // onLocationChange, this counter gets decremented
480 // (so we keep it while switching tabs after failed loads)
481 // We need to add 2 because loadURIWithFlags may have
482 // cancelled a pending load which would have cleared
483 // its anchor scroll detection temporary increment.
484 if (aWebProgress.DOMWindow == this.mBrowser.contentWindow)
485 this.mBrowser.userTypedClear += 2;
488 if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) {
489 this.mTab.setAttribute("busy", "true");
490 this.mTabBrowser.setTabTitleLoading(this.mTab);
493 if (this.mTab.selected)
494 this.mTabBrowser.mIsBusy = true;
497 else if (aStateFlags & nsIWebProgressListener.STATE_STOP &&
498 aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
499 if (aWebProgress.DOMWindow == this.mBrowser.contentWindow) {
500 // The document is done loading, we no longer want the
502 if (this.mBrowser.userTypedClear > 1)
503 this.mBrowser.userTypedClear -= 2;
504 else if (this.mBrowser.userTypedClear > 0)
505 this.mBrowser.userTypedClear--;
507 if (!this.mBrowser.mIconURL)
508 this.mTabBrowser.useDefaultIcon(this.mTab);
514 this.mTab.removeAttribute("busy");
515 this.mTab.removeAttribute("progress");
517 var location = aRequest.QueryInterface(nsIChannel).URI;
519 // For keyword URIs clear the user typed value since they will be changed into real URIs
520 if (location.scheme == "keyword")
521 this.mBrowser.userTypedValue = null;
523 if (this.mTab.label == this.mTabBrowser.mStringBundle.getString("tabs.connecting"))
524 this.mTabBrowser.setTabTitle(this.mTab);
526 if (this.mTab.selected)
527 this.mTabBrowser.mIsBusy = false;
531 this._callProgressListeners("onUpdateCurrentBrowser",
532 [aStateFlags, aStatus, "", 0],
535 this._callProgressListeners("onStateChange",
536 [aWebProgress, aRequest, aStateFlags, aStatus],
540 this._callProgressListeners("onStateChange",
541 [aWebProgress, aRequest, aStateFlags, aStatus],
544 if (aStateFlags & (nsIWebProgressListener.STATE_START |
545 nsIWebProgressListener.STATE_STOP)) {
546 // reset cached temporary values at beginning and end
548 this.mTotalProgress = 0;
550 this.mStateFlags = aStateFlags;
551 this.mStatus = aStatus;
554 onLocationChange: function (aWebProgress, aRequest, aLocation) {
555 // OnLocationChange is called for both the top-level content
556 // and the subframes.
557 let topLevel = aWebProgress.DOMWindow == this.mBrowser.contentWindow;
560 // The document loaded correctly, clear the value if we should
561 if (this.mBrowser.userTypedClear > 0)
562 this.mBrowser.userTypedValue = null;
564 // Clear out the missing plugins list since it's related to the
565 // previous location.
566 this.mBrowser.missingPlugins = null;
568 // Don't clear the favicon if this onLocationChange was
569 // triggered by a pushState or a replaceState. See bug 550565.
570 if (aWebProgress.isLoadingDocument &&
571 !(this.mBrowser.docShell.loadType & Ci.nsIDocShell.LOAD_CMD_PUSHSTATE))
572 this.mBrowser.mIconURL = null;
574 let autocomplete = this.mTabBrowser._placesAutocomplete;
575 if (this.mBrowser.registeredOpenURI) {
576 autocomplete.unregisterOpenPage(this.mBrowser.registeredOpenURI);
577 delete this.mBrowser.registeredOpenURI;
579 if (aLocation.spec != "about:blank") {
580 autocomplete.registerOpenPage(aLocation);
581 this.mBrowser.registeredOpenURI = aLocation;
586 this._callProgressListeners("onLocationChange",
587 [aWebProgress, aRequest, aLocation]);
591 this.mBrowser.lastURI = aLocation;
594 onStatusChange: function (aWebProgress, aRequest, aStatus, aMessage) {
598 this._callProgressListeners("onStatusChange",
599 [aWebProgress, aRequest, aStatus, aMessage]);
601 this.mMessage = aMessage;
604 onSecurityChange: function (aWebProgress, aRequest, aState) {
605 this._callProgressListeners("onSecurityChange",
606 [aWebProgress, aRequest, aState]);
609 onRefreshAttempted: function (aWebProgress, aURI, aDelay, aSameURI) {
610 return this._callProgressListeners("onRefreshAttempted",
611 [aWebProgress, aURI, aDelay, aSameURI]);
614 QueryInterface: function (aIID) {
615 if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
616 aIID.equals(Components.interfaces.nsIWebProgressListener2) ||
617 aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
618 aIID.equals(Components.interfaces.nsISupports))
620 throw Components.results.NS_NOINTERFACE;
627 <method name="setIcon">
628 <parameter name="aTab"/>
629 <parameter name="aURI"/>
632 var browser = this.getBrowserForTab(aTab);
633 browser.mIconURL = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
635 if (aURI && this.mFaviconService) {
636 if (!(aURI instanceof Ci.nsIURI))
637 aURI = makeURI(aURI);
638 this.mFaviconService.setAndLoadFaviconForPage(browser.currentURI,
642 if ((browser.mIconURL || "") != aTab.getAttribute("image")) {
643 if (browser.mIconURL)
644 aTab.setAttribute("image", browser.mIconURL);
646 aTab.removeAttribute("image");
647 this._tabAttrModified(aTab);
650 this._callProgressListeners(browser, "onLinkIconAvailable", [browser.mIconURL]);
655 <method name="getIcon">
656 <parameter name="aTab"/>
659 let browser = aTab ? this.getBrowserForTab(aTab) : this.selectedBrowser;
660 return browser.mIconURL;
665 <method name="shouldLoadFavIcon">
666 <parameter name="aURI"/>
670 Services.prefs.getBoolPref("browser.chrome.site_icons") &&
671 Services.prefs.getBoolPref("browser.chrome.favicons") &&
672 ("schemeIs" in aURI) && (aURI.schemeIs("http") || aURI.schemeIs("https")));
677 <method name="useDefaultIcon">
678 <parameter name="aTab"/>
681 var browser = this.getBrowserForTab(aTab);
682 var docURIObject = browser.contentDocument.documentURIObject;
684 if (browser.contentDocument instanceof ImageDocument) {
685 if (Services.prefs.getBoolPref("browser.chrome.site_icons")) {
686 let sz = Services.prefs.getIntPref("browser.chrome.image_icons.max_size");
688 let req = browser.contentDocument.imageRequest;
691 req.image.width <= sz &&
692 req.image.height <= sz)
693 icon = browser.currentURI;
697 // Use documentURIObject in the check for shouldLoadFavIcon so that we
698 // do the right thing with about:-style error pages. Bug 453442
699 else if (this.shouldLoadFavIcon(docURIObject)) {
700 let url = docURIObject.prePath + "/favicon.ico";
701 if (!this.isFailedIcon(url))
704 this.setIcon(aTab, icon);
709 <method name="isFailedIcon">
710 <parameter name="aURI"/>
713 if (this.mFaviconService) {
714 if (!(aURI instanceof Ci.nsIURI))
715 aURI = makeURI(aURI);
716 return this.mFaviconService.isFailedFavicon(aURI);
723 <method name="getWindowTitleForBrowser">
724 <parameter name="aBrowser"/>
729 var docElement = this.ownerDocument.documentElement;
730 var sep = docElement.getAttribute("titlemenuseparator");
732 if (aBrowser.docShell.contentViewer)
733 docTitle = aBrowser.contentTitle;
736 docTitle = docElement.getAttribute("titledefault");
738 var modifier = docElement.getAttribute("titlemodifier");
740 newTitle += docElement.getAttribute("titlepreface");
741 newTitle += docTitle;
745 newTitle += modifier;
747 // If location bar is hidden and the URL type supports a host,
748 // add the scheme and host to the title to prevent spoofing.
749 // XXX https://bugzilla.mozilla.org/show_bug.cgi?id=22183#c239
751 if (docElement.getAttribute("chromehidden").indexOf("location") != -1) {
752 var uri = this.mURIFixup.createExposableURI(
753 aBrowser.currentURI);
754 if (uri.scheme == "about")
755 newTitle = uri.spec + sep + newTitle;
757 newTitle = uri.prePath + sep + newTitle;
761 if (window.TabView) {
762 let groupName = TabView.getActiveGroupName();
764 newTitle = groupName + sep + newTitle;
772 <method name="updateTitlebar">
775 if (window.TabView && TabView.isVisible()) {
776 // ToDo: this will be removed when we gain ability to draw to the menu bar.
778 this.ownerDocument.title = TabView.windowTitle;
781 this.ownerDocument.title = this.getWindowTitleForBrowser(this.mCurrentBrowser);
787 <method name="updateCurrentBrowser">
788 <parameter name="aForceUpdate"/>
791 var newBrowser = this.getBrowserAtIndex(this.tabContainer.selectedIndex);
792 if (this.mCurrentBrowser == newBrowser && !aForceUpdate)
795 var oldTab = this.mCurrentTab;
797 // Preview mode should not reset the owner
798 if (!this._previewMode && oldTab != this.selectedTab)
801 if (this._lastRelatedTab) {
802 if (this._lastRelatedTab != this.selectedTab)
803 this._lastRelatedTab.owner = null;
804 this._lastRelatedTab = null;
807 var oldBrowser = this.mCurrentBrowser;
809 oldBrowser.setAttribute("type", "content-targetable");
810 oldBrowser.docShell.isActive = false;
813 var updatePageReport = false;
815 (oldBrowser.pageReport && !newBrowser.pageReport) ||
816 (!oldBrowser.pageReport && newBrowser.pageReport))
817 updatePageReport = true;
819 newBrowser.setAttribute("type", "content-primary");
820 newBrowser.docShell.isActive = true;
821 this.mCurrentBrowser = newBrowser;
822 this.mCurrentTab = this.selectedTab;
823 this.showTab(this.mCurrentTab);
825 if (updatePageReport)
826 this.mCurrentBrowser.updatePageReport();
828 // Update the URL bar.
829 var loc = this.mCurrentBrowser.currentURI;
831 var webProgress = this.mCurrentBrowser.webProgress;
832 var securityUI = this.mCurrentBrowser.securityUI;
834 this._callProgressListeners(null, "onLocationChange",
835 [webProgress, null, loc], true, false);
838 this._callProgressListeners(null, "onSecurityChange",
839 [webProgress, null, securityUI.state], true, false);
842 var listener = this.mTabListeners[this.tabContainer.selectedIndex] || null;
843 if (listener && listener.mStateFlags) {
844 this._callProgressListeners(null, "onUpdateCurrentBrowser",
845 [listener.mStateFlags, listener.mStatus,
846 listener.mMessage, listener.mTotalProgress],
850 // Don't switch the fast find or update the titlebar (bug 540248) - this tab switch is temporary
851 if (!this._previewMode) {
852 this._fastFind.setDocShell(this.mCurrentBrowser.docShell);
854 this.updateTitlebar();
856 this.mCurrentTab.removeAttribute("titlechanged");
859 // If the new tab is busy, and our current state is not busy, then
860 // we need to fire a start to all progress listeners.
861 const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
862 if (this.mCurrentTab.hasAttribute("busy") && !this.mIsBusy) {
864 this._callProgressListeners(null, "onStateChange",
866 nsIWebProgressListener.STATE_START |
867 nsIWebProgressListener.STATE_IS_NETWORK, 0],
871 // If the new tab is not busy, and our current state is busy, then
872 // we need to fire a stop to all progress listeners.
873 if (!this.mCurrentTab.hasAttribute("busy") && this.mIsBusy) {
874 this.mIsBusy = false;
875 this._callProgressListeners(null, "onStateChange",
877 nsIWebProgressListener.STATE_STOP |
878 nsIWebProgressListener.STATE_IS_NETWORK, 0],
882 // TabSelect events are suppressed during preview mode to avoid confusing extensions and other bits of code
883 // that might rely upon the other changes suppressed.
884 // Focus is suppressed in the event that the main browser window is minimized - focusing a tab would restore the window
885 if (!this._previewMode) {
886 // We've selected the new tab, so go ahead and notify listeners.
887 let event = document.createEvent("Events");
888 event.initEvent("TabSelect", true, false);
889 this.mCurrentTab.dispatchEvent(event);
891 this._tabAttrModified(oldTab);
892 this._tabAttrModified(this.mCurrentTab);
896 // Focus the location bar if it was previously focused for that tab.
897 // In full screen mode, only bother making the location bar visible
898 // if the tab is a blank one.
899 oldBrowser._urlbarFocused = (gURLBar && gURLBar.focused);
900 if (newBrowser._urlbarFocused && gURLBar) {
901 if (!window.fullScreen) {
904 } else if (isTabEmpty(this.mCurrentTab)) {
905 focusAndSelectUrlBar();
910 // If the find bar is focused, keep it focused.
911 if (gFindBarInitialized &&
913 gFindBar.getElement("findbar-textbox").getAttribute("focused") == "true")
916 // Otherwise, focus the content area.
917 let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
918 let newFocusedElement = fm.getFocusedElementForWindow(window.content, true, {});
920 // for anchors, use FLAG_SHOWRING so that it is clear what link was
921 // last clicked when switching back to that tab
922 let focusFlags = fm.FLAG_NOSCROLL;
923 if (newFocusedElement &&
924 (newFocusedElement instanceof HTMLAnchorElement ||
925 newFocusedElement.getAttributeNS("http://www.w3.org/1999/xlink", "type") == "simple"))
926 focusFlags |= fm.FLAG_SHOWRING;
927 fm.setFocus(newBrowser, focusFlags);
934 <method name="_tabAttrModified">
935 <parameter name="aTab"/>
937 if (this._removingTabs.indexOf(aTab) > -1)
940 // This event should be dispatched when any of these attributes change:
941 // label, crop, busy, image, selected
942 var event = document.createEvent("Events");
943 event.initEvent("TabAttrModified", true, false);
944 aTab.dispatchEvent(event);
948 <method name="setTabTitleLoading">
949 <parameter name="aTab"/>
952 aTab.label = this.mStringBundle.getString("tabs.connecting");
954 this._tabAttrModified(aTab);
959 <method name="setTabTitle">
960 <parameter name="aTab"/>
963 var browser = this.getBrowserForTab(aTab);
965 var title = browser.contentTitle;
968 if (browser.currentURI.spec) {
970 title = this.mURIFixup.createExposableURI(browser.currentURI).spec;
972 title = browser.currentURI.spec;
976 if (title && title != "about:blank") {
977 // At this point, we now have a URI.
978 // Let's try to unescape it using a character set
979 // in case the URI is not ASCII.
981 var characterSet = browser.contentDocument.characterSet;
982 const textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"]
983 .getService(Components.interfaces.nsITextToSubURI);
984 title = textToSubURI.unEscapeNonAsciiURI(characterSet, title);
985 } catch(ex) { /* Do nothing. */ }
989 } else // Still no title? Fall back to our untitled string.
990 title = this.mStringBundle.getString("tabs.emptyTabTitle");
993 if (aTab.label == title &&
999 this._tabAttrModified(aTab);
1002 this.updateTitlebar();
1009 <method name="enterTabbedMode">
1012 if (this.mTabbedMode)
1015 this.mTabbedMode = true; // Welcome to multi-tabbed mode.
1017 if (XULBrowserWindow.isBusy) {
1018 this.mCurrentTab.setAttribute("busy", "true");
1019 this.mIsBusy = true;
1020 this.setTabTitleLoading(this.mCurrentTab);
1022 this.setIcon(this.mCurrentTab, this.mCurrentBrowser.mIconURL);
1026 if (this.mTabFilters.length > 0) {
1027 // Use the filter hooked up in our addProgressListener
1028 filter = this.mTabFilters[0];
1030 // create a filter and hook it up to our first browser
1031 filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
1032 .createInstance(Components.interfaces.nsIWebProgress);
1033 this.mTabFilters[0] = filter;
1034 this.mCurrentBrowser.webProgress.addProgressListener(filter, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
1037 // Remove all our progress listeners from the active browser's filter.
1038 this.mProgressListeners.forEach(filter.removeProgressListener, filter);
1040 // Wire up a progress listener to our filter.
1041 const listener = this.mTabProgressListener(this.mCurrentTab,
1042 this.mCurrentBrowser,
1044 filter.addProgressListener(listener, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
1045 this.mTabListeners[0] = listener;
1050 <method name="loadOneTab">
1051 <parameter name="aURI"/>
1052 <parameter name="aReferrerURI"/>
1053 <parameter name="aCharset"/>
1054 <parameter name="aPostData"/>
1055 <parameter name="aLoadInBackground"/>
1056 <parameter name="aAllowThirdPartyFixup"/>
1060 var aRelatedToCurrent;
1061 if (arguments.length == 2 &&
1062 typeof arguments[1] == "object" &&
1063 !(arguments[1] instanceof Ci.nsIURI)) {
1064 let params = arguments[1];
1065 aReferrerURI = params.referrerURI;
1066 aCharset = params.charset;
1067 aPostData = params.postData;
1068 aLoadInBackground = params.inBackground;
1069 aAllowThirdPartyFixup = params.allowThirdPartyFixup;
1070 aFromExternal = params.fromExternal;
1071 aRelatedToCurrent = params.relatedToCurrent;
1074 var bgLoad = (aLoadInBackground != null) ? aLoadInBackground :
1075 Services.prefs.getBoolPref("browser.tabs.loadInBackground");
1076 var owner = bgLoad ? null : this.selectedTab;
1077 var tab = this.addTab(aURI, {
1078 referrerURI: aReferrerURI,
1080 postData: aPostData,
1082 allowThirdPartyFixup: aAllowThirdPartyFixup,
1083 fromExternal: aFromExternal,
1084 relatedToCurrent: aRelatedToCurrent});
1086 this.selectedTab = tab;
1093 <method name="loadTabs">
1094 <parameter name="aURIs"/>
1095 <parameter name="aLoadInBackground"/>
1096 <parameter name="aReplace"/>
1101 // The tab selected after this new tab is closed (i.e. the new tab's
1102 // "owner") is the next adjacent tab (i.e. not the previously viewed tab)
1103 // when several urls are opened here (i.e. closing the first should select
1104 // the next of many URLs opened) or if the pref to have UI links opened in
1105 // the background is set (i.e. the link is not being opened modally)
1108 // Number of URLs Load UI Links in BG Focus Last Viewed?
1111 // > 1 false/true NO
1112 var multiple = aURIs.length > 1;
1113 var owner = multiple || aLoadInBackground ? null : this.selectedTab;
1114 var firstTabAdded = null;
1118 this.loadURI(aURIs[0], null, null);
1120 // Ignore failure in case a URI is wrong, so we can continue
1121 // opening the next ones.
1125 firstTabAdded = this.addTab(aURIs[0], {ownerTab: owner, skipAnimation: multiple});
1127 var tabNum = this.tabContainer.selectedIndex;
1128 for (let i = 1; i < aURIs.length; ++i) {
1129 let tab = this.addTab(aURIs[i], {skipAnimation: true});
1131 this.moveTabTo(tab, ++tabNum);
1134 if (!aLoadInBackground) {
1135 if (firstTabAdded) {
1136 // .selectedTab setter focuses the content area
1137 this.selectedTab = firstTabAdded;
1140 window.content.focus();
1145 <method name="addTab">
1146 <parameter name="aURI"/>
1147 <parameter name="aReferrerURI"/>
1148 <parameter name="aCharset"/>
1149 <parameter name="aPostData"/>
1150 <parameter name="aOwner"/>
1151 <parameter name="aAllowThirdPartyFixup"/>
1155 var aRelatedToCurrent;
1157 if (arguments.length == 2 &&
1158 typeof arguments[1] == "object" &&
1159 !(arguments[1] instanceof Ci.nsIURI)) {
1160 let params = arguments[1];
1161 aReferrerURI = params.referrerURI;
1162 aCharset = params.charset;
1163 aPostData = params.postData;
1164 aOwner = params.ownerTab;
1165 aAllowThirdPartyFixup = params.allowThirdPartyFixup;
1166 aFromExternal = params.fromExternal;
1167 aRelatedToCurrent = params.relatedToCurrent;
1168 aSkipAnimation = params.skipAnimation;
1171 this._browsers = null; // invalidate cache
1173 this.enterTabbedMode();
1175 // if we're adding tabs, we're past interrupt mode, ditch the owner
1176 if (this.mCurrentTab.owner)
1177 this.mCurrentTab.owner = null;
1179 var t = document.createElementNS(
1180 "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
1183 var blank = !aURI || (aURI == "about:blank");
1186 t.setAttribute("label", this.mStringBundle.getString("tabs.emptyTabTitle"));
1188 t.setAttribute("label", aURI);
1190 t.setAttribute("crop", "end");
1191 t.setAttribute("validate", "never");
1192 t.setAttribute("onerror", "this.removeAttribute('image');");
1193 t.className = "tabbrowser-tab";
1195 // When overflowing, new tabs are scrolled into view smoothly, which
1196 // doesn't go well together with the width transition. So we skip the
1197 // transition in that case.
1198 if (aSkipAnimation ||
1199 this.tabContainer.getAttribute("overflow") == "true" ||
1200 !Services.prefs.getBoolPref("browser.tabs.animate")) {
1201 t.setAttribute("fadein", "true");
1202 setTimeout(function (tabContainer) {
1203 tabContainer._handleNewTab(t);
1204 }, 0, this.tabContainer);
1206 setTimeout(function (tabContainer) {
1208 tabContainer._handleNewTab(t);
1210 t.setAttribute("fadein", "true");
1211 }, 0, this.tabContainer);
1214 this.tabContainer.appendChild(t);
1215 if (this.tabContainer.getAttribute("pinnedonly") == "true")
1216 this.tabContainer._positionPinnedTabs();
1218 if (this.tabContainer.mTabstrip._isRTLScrollbox) {
1219 /* In RTL UI, the tab is visually added to the left side of the
1220 * tabstrip. This means the tabstip has to be scrolled back in
1221 * order to make sure the same set of tabs is visible before and
1222 * after the new tab is added. See bug 508816. */
1224 this.tabContainer.mTabstrip.scrollByPixels(t.clientWidth);
1227 // invalidate cache, because tabContainer is about to change
1228 this._browsers = null;
1230 // If this new tab is owned by another, assert that relationship
1234 var b = document.createElementNS(
1235 "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
1237 b.setAttribute("type", "content-targetable");
1238 b.setAttribute("message", "true");
1239 b.setAttribute("contextmenu", this.getAttribute("contentcontextmenu"));
1240 b.setAttribute("tooltip", this.getAttribute("contenttooltip"));
1241 if (this.hasAttribute("autocompletepopup"))
1242 b.setAttribute("autocompletepopup", this.getAttribute("autocompletepopup"));
1243 b.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
1245 // Create the browserStack container
1246 var stack = document.createElementNS(
1247 "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
1249 stack.setAttribute("anonid", "browserStack");
1250 stack.appendChild(b);
1251 stack.setAttribute("flex", "1");
1253 // Add the Message and the Browser to the box
1254 var notificationbox = document.createElementNS(
1255 "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
1257 notificationbox.setAttribute("flex", "1");
1258 notificationbox.appendChild(stack);
1260 var position = this.tabs.length - 1;
1261 var uniqueId = "panel" + Date.now() + position;
1262 notificationbox.id = uniqueId;
1263 t.linkedPanel = uniqueId;
1264 t.linkedBrowser = b;
1266 if (t.previousSibling.selected)
1267 t.setAttribute("afterselected", true);
1269 // NB: this appendChild call causes us to run constructors for the
1270 // browser element, which fires off a bunch of notifications. Some
1271 // of those notifications can cause code to run that inspects our
1272 // state, so it is important that the tab element is fully
1273 // initialized by this point.
1274 this.mPanelContainer.appendChild(notificationbox);
1276 this.tabContainer.updateVisibility();
1278 // wire up a progress listener for the new browser object.
1279 var tabListener = this.mTabProgressListener(t, b, blank);
1280 const filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
1281 .createInstance(Components.interfaces.nsIWebProgress);
1282 filter.addProgressListener(tabListener, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
1283 b.webProgress.addProgressListener(filter, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
1284 this.mTabListeners[position] = tabListener;
1285 this.mTabFilters[position] = filter;
1287 b._fastFind = this.fastFind;
1288 b.droppedLinkHandler = handleDroppedLink;
1290 // Dispatch a new tab notification. We do this once we're
1291 // entirely done, so that things are in a consistent state
1292 // even if the event listener opens or closes tabs.
1293 var evt = document.createEvent("Events");
1294 evt.initEvent("TabOpen", true, false);
1295 t.dispatchEvent(evt);
1298 // Stop the existing about:blank load. Otherwise, if aURI
1299 // doesn't stop in-progress loads on its own, we'll get into
1300 // trouble with multiple parallel loads running at once.
1303 // pretend the user typed this so it'll be available till
1304 // the document successfully loads
1305 b.userTypedValue = aURI;
1307 let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
1308 if (aAllowThirdPartyFixup)
1309 flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
1311 flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL;
1313 b.loadURIWithFlags(aURI, flags, aReferrerURI, aCharset, aPostData);
1319 // We start our browsers out as inactive, and then maintain
1320 // activeness in the tab switcher.
1321 b.docShell.isActive = false;
1323 // Check if we're opening a tab related to the current tab and
1324 // move it to after the current tab.
1325 // aReferrerURI is null or undefined if the tab is opened from
1326 // an external application or bookmark, i.e. somewhere other
1327 // than the current tab.
1328 if ((aRelatedToCurrent == null ? aReferrerURI : aRelatedToCurrent) &&
1329 Services.prefs.getBoolPref("browser.tabs.insertRelatedAfterCurrent")) {
1330 let newTabPos = (this._lastRelatedTab ||
1331 this.selectedTab)._tPos + 1;
1332 if (this._lastRelatedTab)
1333 this._lastRelatedTab.owner = null;
1335 t.owner = this.selectedTab;
1336 this.moveTabTo(t, newTabPos);
1337 this._lastRelatedTab = t;
1345 <method name="warnAboutClosingTabs">
1346 <parameter name="aAll"/>
1349 var tabsToClose = (aAll ? this.tabs.length : this.visibleTabs.length - 1)
1350 - gBrowser._numPinnedTabs;
1351 if (tabsToClose <= 1)
1354 const pref = "browser.tabs.warnOnClose";
1355 var shouldPrompt = Services.prefs.getBoolPref(pref);
1360 var ps = Services.prompt;
1362 // default to true: if it were false, we wouldn't get this far
1363 var warnOnClose = { value: true };
1364 var bundle = this.mStringBundle;
1366 // focus the window before prompting.
1367 // this will raise any minimized window, which will
1368 // make it obvious which window the prompt is for and will
1369 // solve the problem of windows "obscuring" the prompt.
1370 // see bug #350299 for more details
1373 ps.confirmEx(window,
1374 bundle.getString("tabs.closeWarningTitle"),
1375 bundle.getFormattedString("tabs.closeWarningMultipleTabs",
1377 (ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_0)
1378 + (ps.BUTTON_TITLE_CANCEL * ps.BUTTON_POS_1),
1379 bundle.getString("tabs.closeButtonMultiple"),
1381 bundle.getString('tabs.closeWarningPromptMe'),
1383 var reallyClose = (buttonPressed == 0);
1384 // don't set the pref unless they press OK and it's false
1385 if (reallyClose && !warnOnClose.value)
1386 Services.prefs.setBoolPref(pref, false);
1393 <method name="removeAllTabsBut">
1394 <parameter name="aTab"/>
1400 if (this.warnAboutClosingTabs(false)) {
1401 let tabs = this.visibleTabs;
1402 this.selectedTab = aTab;
1404 for (let i = tabs.length - 1; i >= 0; --i) {
1405 if (tabs[i] != aTab && !tabs[i].pinned)
1406 this.removeTab(tabs[i]);
1413 <method name="removeCurrentTab">
1414 <parameter name="aParams"/>
1417 this.removeTab(this.mCurrentTab, aParams);
1422 <field name="_removingTabs">
1426 <method name="removeTab">
1427 <parameter name="aTab"/>
1428 <parameter name="aParams"/>
1432 var animate = aParams.animate;
1434 // Handle requests for synchronously removing an already
1435 // asynchronously closing tab.
1437 this._removingTabs.indexOf(aTab) > -1) {
1438 this._endRemoveTab(aTab);
1442 var isLastTab = (this.tabs.length - this._removingTabs.length == 1);
1444 if (!this._beginRemoveTab(aTab, false, null, true))
1447 if (!animate /* the caller didn't opt in */ ||
1450 this._removingTabs.length > 3 /* don't want lots of concurrent animations */ ||
1451 aTab.getAttribute("fadein") != "true" /* fade-in transition hasn't been triggered yet */ ||
1452 window.getComputedStyle(aTab).maxWidth == "0.1px" /* fade-in transition hasn't moved yet */ ||
1453 !Services.prefs.getBoolPref("browser.tabs.animate")) {
1454 this._endRemoveTab(aTab);
1458 this._blurTab(aTab);
1459 aTab.removeAttribute("fadein");
1461 setTimeout(function (tab, tabbrowser) {
1462 if (tab.parentNode &&
1463 window.getComputedStyle(tab).maxWidth == "0.1px") {
1464 NS_ASSERT(false, "Giving up waiting for the tab closing animation to finish (bug 608589)");
1465 tabbrowser._endRemoveTab(tab);
1467 }, 3000, aTab, this);
1472 <!-- Tab close requests are ignored if the window is closing anyway,
1473 e.g. when holding Ctrl+W. -->
1474 <field name="_windowIsClosing">
1478 <method name="_beginRemoveTab">
1479 <parameter name="aTab"/>
1480 <parameter name="aTabWillBeMoved"/>
1481 <parameter name="aCloseWindowWithLastTab"/>
1482 <parameter name="aCloseWindowFastpath"/>
1485 if (this._removingTabs.indexOf(aTab) > -1 || this._windowIsClosing)
1488 var browser = this.getBrowserForTab(aTab);
1490 if (!aTabWillBeMoved) {
1491 let ds = browser.docShell;
1492 if (ds && ds.contentViewer && !ds.contentViewer.permitUnload())
1496 var closeWindow = false;
1498 if (this.tabs.length - this._removingTabs.length == 1) {
1499 closeWindow = aCloseWindowWithLastTab != null ? aCloseWindowWithLastTab :
1500 !window.toolbar.visible ||
1501 this.tabContainer._closeWindowWithLastTab;
1503 // Closing the tab and replacing it with a blank one is notably slower
1504 // than closing the window right away. If the caller opts in, take
1507 aCloseWindowFastpath &&
1508 this._removingTabs.length == 0 &&
1509 (this._windowIsClosing = window.closeWindow(true)))
1515 this._removingTabs.push(aTab);
1517 this.tabContainer.updateVisibility();
1519 // We're committed to closing the tab now.
1520 // Dispatch a notification.
1521 // We dispatch it before any teardown so that event listeners can
1522 // inspect the tab that's about to close.
1523 var evt = document.createEvent("UIEvent");
1524 evt.initUIEvent("TabClose", true, false, window, aTabWillBeMoved ? 1 : 0);
1525 aTab.dispatchEvent(evt);
1527 // Remove the tab's filter and progress listener.
1528 const filter = this.mTabFilters[aTab._tPos];
1529 browser.webProgress.removeProgressListener(filter);
1530 filter.removeProgressListener(this.mTabListeners[aTab._tPos]);
1531 this.mTabListeners[aTab._tPos].destroy();
1533 if (browser.registeredOpenURI && !aTabWillBeMoved) {
1534 this._placesAutocomplete.unregisterOpenPage(browser.registeredOpenURI);
1535 delete browser.registeredOpenURI;
1538 // We are no longer the primary content area.
1539 browser.setAttribute("type", "content-targetable");
1541 // Remove this tab as the owner of any other tabs, since it's going away.
1542 Array.forEach(this.tabs, function (tab) {
1543 if ("owner" in tab && tab.owner == aTab)
1544 // |tab| is a child of the tab we're removing, make it an orphan
1548 aTab._endRemoveArgs = [closeWindow, newTab];
1554 <method name="_endRemoveTab">
1555 <parameter name="aTab"/>
1558 if (!aTab || !aTab._endRemoveArgs)
1561 var [aCloseWindow, aNewTab] = aTab._endRemoveArgs;
1562 aTab._endRemoveArgs = null;
1564 if (this._windowIsClosing) {
1565 aCloseWindow = false;
1569 this._lastRelatedTab = null;
1571 // update the UI early for responsiveness
1572 aTab.collapsed = true;
1574 this.addTab("about:blank", {skipAnimation: true});
1575 this.tabContainer._fillTrailingGap();
1576 this._blurTab(aTab);
1578 this._removingTabs.splice(this._removingTabs.indexOf(aTab), 1);
1581 this._windowIsClosing = true;
1582 while (this._removingTabs.length)
1583 this._endRemoveTab(this._removingTabs[0]);
1584 } else if (!this._windowIsClosing) {
1586 focusAndSelectUrlBar();
1588 // workaround for bug 345399
1589 this.tabContainer.mTabstrip._updateScrollButtonsDisabledState();
1592 // We're going to remove the tab and the browser now.
1593 // Clean up mTabFilters and mTabListeners now rather than in
1594 // _beginRemoveTab, so that their size is always in sync with the
1595 // number of tabs and browsers (the xbl destructor depends on this).
1596 this.mTabFilters.splice(aTab._tPos, 1);
1597 this.mTabListeners.splice(aTab._tPos, 1);
1599 var browser = this.getBrowserForTab(aTab);
1601 // Because of the way XBL works (fields just set JS
1602 // properties on the element) and the code we have in place
1603 // to preserve the JS objects for any elements that have
1604 // JS properties set on them, the browser element won't be
1605 // destroyed until the document goes away. So we force a
1606 // cleanup ourselves.
1607 // This has to happen before we remove the child so that the
1608 // XBL implementation of nsIObserver still works.
1611 if (browser == this.mCurrentBrowser)
1612 this.mCurrentBrowser = null;
1614 // Invalidate browsers cache, as the tab is removed from the
1616 this._browsers = null;
1618 // Remove the tab ...
1619 this.tabContainer.removeChild(aTab);
1621 // ... and fix up the _tPos properties immediately.
1622 for (let i = aTab._tPos; i < this.tabs.length; i++)
1623 this.tabs[i]._tPos = i;
1625 if (!this._windowIsClosing) {
1626 this.tabContainer._positionPinnedTabs();
1628 // update tab close buttons state
1629 this.tabContainer.adjustTabstrip();
1632 // update first-tab/last-tab/beforeselected/afterselected attributes
1633 this.selectedTab._selected = true;
1635 // Removing the panel requires fixing up selectedPanel immediately
1636 // (see below), which would be hindered by the potentially expensive
1637 // browser removal. So we remove the browser and the panel in two
1639 var panel = browser.parentNode.parentNode;
1641 // This will unload the document. An unload handler could remove
1642 // dependant tabs, so it's important that the tabbrowser is now in
1643 // a consistent state (tab removed, tab positions updated, etc.).
1644 panel.removeChild(browser.parentNode);
1646 // As the browser is removed, the removal of a dependent document can
1647 // cause the whole window to close. So at this point, it's possible
1648 // that the binding is destructed.
1650 let selectedPanel = this.mTabBox.selectedPanel;
1652 this.mPanelContainer.removeChild(panel);
1654 // Under the hood, a selectedIndex attribute controls which panel
1655 // is displayed. Removing a panel A which precedes the selected
1656 // panel B makes selectedIndex point to the panel next to B. We
1657 // need to explicitly preserve B as the selected panel.
1658 this.mTabBox.selectedPanel = selectedPanel;
1662 this._windowIsClosing = closeWindow(true);
1667 <method name="_blurTab">
1668 <parameter name="aTab"/>
1671 if (this.mCurrentTab != aTab)
1675 this._removingTabs.indexOf(aTab.owner) == -1 &&
1676 Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose")) {
1677 this.selectedTab = aTab.owner;
1681 // Switch to a visible tab unless there aren't any others remaining
1682 let remainingTabs = this.visibleTabs;
1683 let numTabs = remainingTabs.length;
1684 if (numTabs == 0 || numTabs == 1 && remainingTabs[0] == aTab) {
1685 remainingTabs = Array.filter(this.tabs, function(tab) {
1686 return this._removingTabs.indexOf(tab) == -1;
1690 // Try to find a remaining tab that comes after the given tab
1693 tab = tab.nextSibling;
1694 } while (tab && remainingTabs.indexOf(tab) == -1);
1700 tab = tab.previousSibling;
1701 } while (tab && remainingTabs.indexOf(tab) == -1);
1704 this.selectedTab = tab;
1709 <method name="swapBrowsersAndCloseOther">
1710 <parameter name="aOurTab"/>
1711 <parameter name="aOtherTab"/>
1714 // That's gBrowser for the other window, not the tab's browser!
1715 var remoteBrowser = aOtherTab.ownerDocument.defaultView.gBrowser;
1717 // First, start teardown of the other browser. Make sure to not
1718 // fire the beforeunload event in the process. Close the other
1719 // window if this was its last tab.
1720 if (!remoteBrowser._beginRemoveTab(aOtherTab, true, true))
1723 // Unhook our progress listener
1724 var ourIndex = aOurTab._tPos;
1725 const filter = this.mTabFilters[ourIndex];
1726 var tabListener = this.mTabListeners[ourIndex];
1727 var ourBrowser = this.getBrowserForTab(aOurTab);
1728 ourBrowser.webProgress.removeProgressListener(filter);
1729 filter.removeProgressListener(tabListener);
1730 var tabListenerBlank = tabListener.mBlank;
1732 var otherBrowser = aOtherTab.linkedBrowser;
1734 // Restore current registered open URI.
1735 if (ourBrowser.registeredOpenURI) {
1736 this._placesAutocomplete.unregisterOpenPage(ourBrowser.registeredOpenURI);
1737 delete ourBrowser.registeredOpenURI;
1739 if (otherBrowser.registeredOpenURI) {
1740 ourBrowser.registeredOpenURI = otherBrowser.registeredOpenURI;
1741 delete otherBrowser.registeredOpenURI;
1744 // Workarounds for bug 458697
1745 // Icon might have been set on DOMLinkAdded, don't override that.
1746 if (!ourBrowser.mIconURL && otherBrowser.mIconURL)
1747 this.setIcon(aOurTab, otherBrowser.mIconURL);
1748 var isBusy = aOtherTab.hasAttribute("busy");
1750 aOurTab.setAttribute("busy", "true");
1751 this._tabAttrModified(aOurTab);
1752 if (aOurTab == this.selectedTab)
1753 this.mIsBusy = true;
1756 // Swap the docshells
1757 ourBrowser.swapDocShells(otherBrowser);
1759 // Finish tearing down the tab that's going away.
1760 remoteBrowser._endRemoveTab(aOtherTab);
1762 // Restore the progress listener
1763 tabListener = this.mTabProgressListener(aOurTab, ourBrowser,
1765 this.mTabListeners[ourIndex] = tabListener;
1766 filter.addProgressListener(tabListener,
1767 Components.interfaces.nsIWebProgress.NOTIFY_ALL);
1769 ourBrowser.webProgress.addProgressListener(filter,
1770 Components.interfaces.nsIWebProgress.NOTIFY_ALL);
1773 this.setTabTitleLoading(aOurTab);
1775 this.setTabTitle(aOurTab);
1777 // If the tab was already selected (this happpens in the scenario
1778 // of replaceTabWithWindow), notify onLocationChange, etc.
1779 if (aOurTab == this.selectedTab)
1780 this.updateCurrentBrowser(true);
1785 <method name="reloadAllTabs">
1788 let tabs = this.visibleTabs;
1789 let l = tabs.length;
1790 for (var i = 0; i < l; i++) {
1792 this.getBrowserForTab(tabs[i]).reload();
1794 // ignore failure to reload so others will be reloaded
1801 <method name="reloadTab">
1802 <parameter name="aTab"/>
1805 this.getBrowserForTab(aTab).reload();
1810 <method name="addProgressListener">
1811 <parameter name="aListener"/>
1812 <parameter name="aMask"/>
1815 if (!this.mAddProgressListenerWasCalled) {
1816 this.mAddProgressListenerWasCalled = true;
1817 this.tabContainer.updateVisibility();
1820 if (this.mProgressListeners.length == 1) {
1821 // If we are adding a 2nd progress listener, we need to enter tabbed mode
1822 // because the browser status filter can only handle one progress listener.
1823 // In tabbed mode, mTabProgressListener is used which will iterate over all listeners.
1824 this.enterTabbedMode();
1827 this.mProgressListeners.push(aListener);
1829 if (!this.mTabbedMode) {
1830 // If someone does this:
1831 // addProgressListener, removeProgressListener, addProgressListener
1832 // don't create a new filter; reuse the existing filter.
1833 if (this.mTabFilters.length == 0) {
1834 // hook a filter up to our first browser
1835 const filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
1836 .createInstance(Components.interfaces.nsIWebProgress);
1837 this.mTabFilters[0] = filter;
1838 this.mCurrentBrowser.webProgress.addProgressListener(filter, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
1841 // Directly hook the listener up to the filter for better performance
1842 this.mTabFilters[0].addProgressListener(aListener, aMask);
1848 <method name="removeProgressListener">
1849 <parameter name="aListener"/>
1852 this.mProgressListeners =
1853 this.mProgressListeners.filter(function (l) l != aListener);
1855 if (!this.mTabbedMode)
1856 // Don't forget to remove it from the filter we hooked it up to
1857 this.mTabFilters[0].removeProgressListener(aListener);
1862 <method name="addTabsProgressListener">
1863 <parameter name="aListener"/>
1865 this.enterTabbedMode();
1866 this.mTabsProgressListeners.push(aListener);
1870 <method name="removeTabsProgressListener">
1871 <parameter name="aListener"/>
1874 this.mTabsProgressListeners =
1875 this.mTabsProgressListeners.filter(function (l) l != aListener);
1880 <method name="getBrowserForTab">
1881 <parameter name="aTab"/>
1884 return aTab.linkedBrowser;
1889 <method name="showOnlyTheseTabs">
1890 <parameter name="aTabs"/>
1893 Array.forEach(this.tabs, function(tab) {
1894 if (aTabs.indexOf(tab) == -1)
1903 <method name="showTab">
1904 <parameter name="aTab"/>
1908 aTab.removeAttribute("hidden");
1909 this.tabContainer.adjustTabstrip();
1910 let event = document.createEvent("Events");
1911 event.initEvent("TabShow", true, false);
1912 aTab.dispatchEvent(event);
1918 <method name="hideTab">
1919 <parameter name="aTab"/>
1922 if (!aTab.hidden && !aTab.pinned && !aTab.selected &&
1923 this._removingTabs.indexOf(aTab) == -1) {
1924 aTab.setAttribute("hidden", "true");
1925 this.tabContainer.adjustTabstrip();
1926 let event = document.createEvent("Events");
1927 event.initEvent("TabHide", true, false);
1928 aTab.dispatchEvent(event);
1934 <method name="selectTabAtIndex">
1935 <parameter name="aIndex"/>
1936 <parameter name="aEvent"/>
1939 let tabs = this.visibleTabs;
1941 // count backwards for aIndex < 0
1943 aIndex += tabs.length;
1945 if (aIndex >= 0 && aIndex < tabs.length)
1946 this.selectedTab = tabs[aIndex];
1949 aEvent.preventDefault();
1950 aEvent.stopPropagation();
1956 <property name="selectedTab">
1958 return this.mTabBox.selectedTab;
1963 this.mTabBox.selectedTab = val;
1969 <property name="selectedBrowser"
1970 onget="return this.mCurrentBrowser;"
1973 <property name="browsers" readonly="true">
1976 return this._browsers ||
1977 (this._browsers = Array.map(this.tabs, function (tab) tab.linkedBrowser));
1982 <!-- Moves a tab to a new browser window, unless it's already the only tab
1983 in the current window, in which case this will do nothing. -->
1984 <method name="replaceTabWithWindow">
1985 <parameter name="aTab"/>
1988 if (this.tabs.length == 1)
1991 // tell a new window to take the "dropped" tab
1992 return window.openDialog(getBrowserURL(), "_blank", "dialog=no,all", aTab);
1997 <method name="moveTabTo">
1998 <parameter name="aTab"/>
1999 <parameter name="aIndex"/>
2002 var oldPosition = aTab._tPos;
2003 if (oldPosition == aIndex)
2006 // Don't allow mixing pinned and unpinned tabs.
2008 aIndex = Math.min(aIndex, this._numPinnedTabs - 1);
2010 aIndex = Math.max(aIndex, this._numPinnedTabs);
2011 if (oldPosition == aIndex)
2014 this._lastRelatedTab = null;
2016 this.mTabFilters.splice(aIndex, 0, this.mTabFilters.splice(aTab._tPos, 1)[0]);
2017 this.mTabListeners.splice(aIndex, 0, this.mTabListeners.splice(aTab._tPos, 1)[0]);
2019 aIndex = aIndex < aTab._tPos ? aIndex: aIndex+1;
2020 this.mCurrentTab._selected = false;
2021 // use .item() instead of [] because dragging to the end of the strip goes out of
2022 // bounds: .item() returns null (so it acts like appendChild), but [] throws
2023 this.tabContainer.insertBefore(aTab, this.tabs.item(aIndex));
2024 // invalidate cache, because tabContainer is about to change
2025 this._browsers = null;
2027 for (let i = 0; i < this.tabs.length; i++) {
2028 this.tabs[i]._tPos = i;
2029 this.tabs[i]._selected = false;
2031 this.mCurrentTab._selected = true;
2032 this.tabContainer.mTabstrip.ensureElementIsVisible(this.mCurrentTab, false);
2035 this.tabContainer._positionPinnedTabs();
2037 var evt = document.createEvent("UIEvents");
2038 evt.initUIEvent("TabMove", true, false, window, oldPosition);
2039 aTab.dispatchEvent(evt);
2044 <method name="moveTabForward">
2047 var tabPos = this.mCurrentTab._tPos;
2048 if (tabPos < this.browsers.length - 1) {
2049 this.moveTabTo(this.mCurrentTab, tabPos + 1);
2050 this.mCurrentTab.focus();
2052 else if (this.arrowKeysShouldWrap)
2053 this.moveTabToStart();
2058 <method name="moveTabBackward">
2061 var tabPos = this.mCurrentTab._tPos;
2063 this.moveTabTo(this.mCurrentTab, tabPos - 1);
2064 this.mCurrentTab.focus();
2066 else if (this.arrowKeysShouldWrap)
2067 this.moveTabToEnd();
2072 <method name="moveTabToStart">
2075 var tabPos = this.mCurrentTab._tPos;
2077 this.moveTabTo(this.mCurrentTab, 0);
2078 this.mCurrentTab.focus();
2084 <method name="moveTabToEnd">
2087 var tabPos = this.mCurrentTab._tPos;
2088 if (tabPos < this.browsers.length - 1) {
2089 this.moveTabTo(this.mCurrentTab,
2090 this.browsers.length - 1);
2091 this.mCurrentTab.focus();
2097 <method name="moveTabOver">
2098 <parameter name="aEvent"/>
2101 var direction = window.getComputedStyle(this.parentNode, null).direction;
2102 if ((direction == "ltr" && aEvent.keyCode == KeyEvent.DOM_VK_RIGHT) ||
2103 (direction == "rtl" && aEvent.keyCode == KeyEvent.DOM_VK_LEFT))
2104 this.moveTabForward();
2106 this.moveTabBackward();
2111 <method name="duplicateTab">
2112 <parameter name="aTab"/><!-- can be from a different window as well -->
2115 return Cc["@mozilla.org/browser/sessionstore;1"]
2116 .getService(Ci.nsISessionStore)
2117 .duplicateTab(window, aTab);
2122 <!-- BEGIN FORWARDED BROWSER PROPERTIES. IF YOU ADD A PROPERTY TO THE BROWSER ELEMENT
2123 MAKE SURE TO ADD IT HERE AS WELL. -->
2124 <property name="canGoBack"
2125 onget="return this.mCurrentBrowser.canGoBack;"
2128 <property name="canGoForward"
2129 onget="return this.mCurrentBrowser.canGoForward;"
2132 <method name="goBack">
2135 return this.mCurrentBrowser.goBack();
2140 <method name="goForward">
2143 return this.mCurrentBrowser.goForward();
2148 <method name="reload">
2151 return this.mCurrentBrowser.reload();
2156 <method name="reloadWithFlags">
2157 <parameter name="aFlags"/>
2160 return this.mCurrentBrowser.reloadWithFlags(aFlags);
2165 <method name="stop">
2168 return this.mCurrentBrowser.stop();
2173 <!-- throws exception for unknown schemes -->
2174 <method name="loadURI">
2175 <parameter name="aURI"/>
2176 <parameter name="aReferrerURI"/>
2177 <parameter name="aCharset"/>
2180 return this.mCurrentBrowser.loadURI(aURI, aReferrerURI, aCharset);
2185 <!-- throws exception for unknown schemes -->
2186 <method name="loadURIWithFlags">
2187 <parameter name="aURI"/>
2188 <parameter name="aFlags"/>
2189 <parameter name="aReferrerURI"/>
2190 <parameter name="aCharset"/>
2191 <parameter name="aPostData"/>
2194 return this.mCurrentBrowser.loadURIWithFlags(aURI, aFlags, aReferrerURI, aCharset, aPostData);
2199 <method name="goHome">
2202 return this.mCurrentBrowser.goHome();
2207 <property name="homePage">
2210 return this.mCurrentBrowser.homePage;
2215 this.mCurrentBrowser.homePage = val;
2221 <method name="gotoIndex">
2222 <parameter name="aIndex"/>
2225 return this.mCurrentBrowser.gotoIndex(aIndex);
2230 <method name="attachFormFill">
2232 for (var i = 0; i < this.mPanelContainer.childNodes.length; ++i) {
2233 var cb = this.getBrowserAtIndex(i);
2234 cb.attachFormFill();
2239 <method name="detachFormFill">
2241 for (var i = 0; i < this.mPanelContainer.childNodes.length; ++i) {
2242 var cb = this.getBrowserAtIndex(i);
2243 cb.detachFormFill();
2248 <property name="pageReport"
2249 onget="return this.mCurrentBrowser.pageReport;"
2252 <property name="currentURI"
2253 onget="return this.mCurrentBrowser.currentURI;"
2256 <field name="_fastFind">null</field>
2257 <property name="fastFind"
2261 if (!this._fastFind) {
2262 this._fastFind = Components.classes["@mozilla.org/typeaheadfind;1"]
2263 .createInstance(Components.interfaces.nsITypeAheadFind);
2264 this._fastFind.init(this.docShell);
2266 return this._fastFind;
2271 <property name="docShell"
2272 onget="return this.mCurrentBrowser.docShell"
2275 <property name="webNavigation"
2276 onget="return this.mCurrentBrowser.webNavigation"
2279 <property name="webBrowserFind"
2281 onget="return this.mCurrentBrowser.webBrowserFind"/>
2283 <property name="webProgress"
2285 onget="return this.mCurrentBrowser.webProgress"/>
2287 <property name="contentWindow"
2289 onget="return this.mCurrentBrowser.contentWindow"/>
2291 <property name="sessionHistory"
2292 onget="return this.mCurrentBrowser.sessionHistory;"
2295 <property name="markupDocumentViewer"
2296 onget="return this.mCurrentBrowser.markupDocumentViewer;"
2299 <property name="contentViewerEdit"
2300 onget="return this.mCurrentBrowser.contentViewerEdit;"
2303 <property name="contentViewerFile"
2304 onget="return this.mCurrentBrowser.contentViewerFile;"
2307 <property name="documentCharsetInfo"
2308 onget="return this.mCurrentBrowser.documentCharsetInfo;"
2311 <property name="contentDocument"
2312 onget="return this.mCurrentBrowser.contentDocument;"
2315 <property name="contentTitle"
2316 onget="return this.mCurrentBrowser.contentTitle;"
2319 <property name="contentPrincipal"
2320 onget="return this.mCurrentBrowser.contentPrincipal;"
2323 <property name="securityUI"
2324 onget="return this.mCurrentBrowser.securityUI;"
2327 <method name="_handleKeyEvent">
2328 <parameter name="aEvent"/>
2330 if (!aEvent.isTrusted) {
2331 // Don't let untrusted events mess with tabs.
2338 // We need to take care of FAYT-watching as long as the findbar
2339 // isn't initialized. The checks on aEvent are copied from
2340 // _shouldFastFind (see findbar.xml).
2341 if (!gFindBarInitialized &&
2342 !(aEvent.ctrlKey || aEvent.metaKey) &&
2343 !aEvent.getPreventDefault()) {
2344 let charCode = aEvent.charCode;
2346 let char = String.fromCharCode(charCode);
2347 if (char == "'" || char == "/" ||
2348 Services.prefs.getBoolPref("accessibility.typeaheadfind")) {
2349 gFindBar._onBrowserKeypress(aEvent);
2356 if (!aEvent.metaKey)
2360 switch (aEvent.charCode) {
2361 case '}'.charCodeAt(0):
2363 case '{'.charCodeAt(0):
2364 if (window.getComputedStyle(this, null).direction == "ltr")
2366 this.tabContainer.advanceSelectedTab(offset, true);
2367 aEvent.stopPropagation();
2368 aEvent.preventDefault();
2371 if (aEvent.ctrlKey && !aEvent.shiftKey && !aEvent.metaKey &&
2372 aEvent.keyCode == KeyEvent.DOM_VK_F4 &&
2373 this.mTabBox.handleCtrlPageUpDown) {
2374 this.removeCurrentTab({animate: true});
2375 aEvent.stopPropagation();
2376 aEvent.preventDefault();
2382 <property name="userTypedClear"
2383 onget="return this.mCurrentBrowser.userTypedClear;"
2384 onset="return this.mCurrentBrowser.userTypedClear = val;"/>
2386 <property name="userTypedValue"
2387 onget="return this.mCurrentBrowser.userTypedValue;"
2388 onset="return this.mCurrentBrowser.userTypedValue = val;"/>
2390 <method name="createTooltip">
2391 <parameter name="event"/>
2393 event.stopPropagation();
2394 var tab = document.tooltipNode;
2395 if (tab.localName != "tab") {
2396 event.preventDefault();
2399 event.target.setAttribute("label", tab.mOverCloseButton ?
2400 tab.getAttribute("closetabtext") :
2401 tab.getAttribute("label"));
2405 <method name="handleEvent">
2406 <parameter name="aEvent"/>
2408 switch (aEvent.type) {
2410 this._handleKeyEvent(aEvent);
2418 this.mCurrentBrowser = this.mPanelContainer.childNodes[0].firstChild.firstChild;
2419 this.mCurrentTab = this.tabContainer.firstChild;
2420 document.addEventListener("keypress", this, false);
2422 var uniqueId = "panel" + Date.now();
2423 this.mPanelContainer.childNodes[0].id = uniqueId;
2424 this.mCurrentTab.linkedPanel = uniqueId;
2425 this.mCurrentTab._tPos = 0;
2426 this.mCurrentTab.linkedBrowser = this.mCurrentBrowser;
2428 // set up the shared autoscroll popup
2429 this._autoScrollPopup = this.mCurrentBrowser._createAutoScrollPopup();
2430 this._autoScrollPopup.id = "autoscroller";
2431 this.appendChild(this._autoScrollPopup);
2432 this.mCurrentBrowser.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
2433 this.mCurrentBrowser.droppedLinkHandler = handleDroppedLink;
2439 for (var i = 0; i < this.mTabListeners.length; ++i) {
2440 let browser = this.getBrowserAtIndex(i);
2441 if (browser.registeredOpenURI) {
2442 this._placesAutocomplete.unregisterOpenPage(browser.registeredOpenURI);
2443 delete browser.registeredOpenURI;
2445 browser.webProgress.removeProgressListener(this.mTabFilters[i]);
2446 this.mTabFilters[i].removeProgressListener(this.mTabListeners[i]);
2447 this.mTabFilters[i] = null;
2448 this.mTabListeners[i].destroy();
2449 this.mTabListeners[i] = null;
2451 document.removeEventListener("keypress", this, false);
2455 <!-- Deprecated stuff, implemented for backwards compatibility. -->
2456 <method name="setStripVisibilityTo">
2457 <parameter name="aShow"/>
2459 this.tabContainer.visible = aShow;
2462 <method name="getStripVisibility">
2464 return this.tabContainer.visible;
2467 <property name="mContextTab" readonly="true"
2468 onget="return TabContextMenu.contextTab;"/>
2469 <property name="mPrefs" readonly="true"
2470 onget="return Services.prefs;"/>
2471 <property name="mTabContainer" readonly="true"
2472 onget="return this.tabContainer;"/>
2473 <property name="mTabs" readonly="true"
2474 onget="return this.tabs;"/>
2476 - Compatibility hack: several extensions depend on this property to
2477 - access the tab context menu or tab container, so keep that working for
2478 - now. Ideally we can remove this once extensions are using
2479 - tabbrowser.tabContextMenu and tabbrowser.tabContainer directly.
2481 <property name="mStrip" readonly="true">
2486 childNodes: [null, this.tabContextMenu, this.tabContainer],
2487 firstChild: { nextSibling: this.tabContextMenu },
2488 getElementsByAttribute: function (attr, attrValue) {
2489 if (attr == "anonid" && attrValue == "tabContextMenu")
2490 return [this.self.tabContextMenu];
2493 // Also support adding event listeners (forward to the tab container)
2494 addEventListener: function (a,b,c) { this.self.tabContainer.addEventListener(a,b,c); },
2495 removeEventListener: function (a,b,c) { this.self.tabContainer.removeEventListener(a,b,c); }
2503 <handler event="DOMWindowClose" phase="capturing">
2505 if (!event.isTrusted)
2508 if (this.tabs.length == 1)
2511 var tab = this._getTabForContentWindow(event.target);
2513 this.removeTab(tab);
2514 event.preventDefault();
2518 <handler event="DOMWillOpenModalDialog" phase="capturing">
2520 if (!event.isTrusted)
2523 // We're about to open a modal dialog, make sure the opening
2524 // tab is brought to the front.
2525 this.selectedTab = this._getTabForContentWindow(event.target.top);
2528 <handler event="DOMTitleChanged">
2530 if (!event.isTrusted)
2533 var contentWin = event.target.defaultView;
2534 if (contentWin != contentWin.top)
2537 var tab = this._getTabForContentWindow(contentWin);
2538 var titleChanged = this.setTabTitle(tab);
2539 if (titleChanged && !tab.selected && !tab.hasAttribute("busy"))
2540 tab.setAttribute("titlechanged", "true");
2546 <binding id="tabbrowser-tabbox"
2547 extends="chrome://global/content/bindings/tabbox.xml#tabbox">
2549 <property name="tabs" readonly="true"
2550 onget="return document.getBindingParent(this).tabContainer;"/>
2554 <binding id="tabbrowser-arrowscrollbox" extends="chrome://global/content/bindings/scrollbox.xml#arrowscrollbox-clicktoscroll">
2556 <!-- Override scrollbox.xml method, since our scrollbox's children are
2557 inherited from the binding parent -->
2558 <method name="_getScrollableElements">
2560 return Array.filter(document.getBindingParent(this).childNodes,
2561 this._canScrollToElement, this);
2564 <method name="_canScrollToElement">
2565 <parameter name="tab"/>
2567 return !tab.pinned && !tab.hidden;
2573 <handler event="underflow"><![CDATA[
2574 if (event.detail == 0)
2575 return; // Ignore vertical events
2577 var tabs = document.getBindingParent(this);
2578 tabs.removeAttribute("overflow");
2580 tabs.tabbrowser._removingTabs.forEach(tabs.tabbrowser.removeTab,
2583 tabs._positionPinnedTabs();
2585 <handler event="overflow"><![CDATA[
2586 if (event.detail == 0)
2587 return; // Ignore vertical events
2589 var tabs = document.getBindingParent(this);
2590 tabs.setAttribute("overflow", "true");
2591 tabs._positionPinnedTabs();
2596 <binding id="tabbrowser-tabs"
2597 extends="chrome://global/content/bindings/tabbox.xml#tabs">
2599 <stylesheet src="chrome://browser/content/tabbrowser.css"/>
2603 <xul:hbox align="start">
2604 <xul:image class="tab-drop-indicator" anonid="tab-drop-indicator" collapsed="true"/>
2606 <xul:arrowscrollbox anonid="arrowscrollbox" orient="horizontal" flex="1"
2607 style="min-width: 1px;"
2609 clicktoscroll="true"
2611 class="tabbrowser-arrowscrollbox">
2612 # This is a hack to circumvent bug 472020, otherwise the tabs show up on the
2613 # right of the newtab button.
2614 <children includes="tab"/>
2615 # This is to ensure anything extensions put here will go before the newtab
2616 # button, necessary due to the previous hack.
2618 <xul:toolbarbutton class="tabs-newtab-button"
2619 command="cmd_newNavigatorTab"
2620 onclick="checkForMiddleClick(this, event);"
2621 tooltiptext="&newTabButton.tooltip;"/>
2622 </xul:arrowscrollbox>
2625 <implementation implements="nsIDOMEventListener">
2628 this.mTabClipWidth = Services.prefs.getIntPref("browser.tabs.tabClipWidth");
2629 this.mCloseButtons = Services.prefs.getIntPref("browser.tabs.closeButtons");
2630 this._closeWindowWithLastTab = Services.prefs.getBoolPref("browser.tabs.closeWindowWithLastTab");
2632 var tab = this.firstChild;
2633 tab.setAttribute("label",
2634 this.tabbrowser.mStringBundle.getString("tabs.emptyTabTitle"));
2635 tab.setAttribute("crop", "end");
2636 tab.setAttribute("validate", "never");
2637 tab.setAttribute("onerror", "this.removeAttribute('image');");
2638 this.adjustTabstrip();
2640 Services.prefs.addObserver("browser.tabs.", this._prefObserver, false);
2641 window.addEventListener("resize", this, false);
2643 if (window.TabsInTitlebar)
2644 TabsInTitlebar.allowedBy("tabs-visible", this.visible);
2650 Services.prefs.removeObserver("browser.tabs.", this._prefObserver);
2654 <field name="tabbrowser" readonly="true">
2655 document.getElementById(this.getAttribute("tabbrowser"));
2658 <field name="tabbox" readonly="true">
2659 this.tabbrowser.mTabBox;
2662 <field name="contextMenu" readonly="true">
2663 document.getElementById("tabContextMenu");
2666 <field name="mTabstripWidth">0</field>
2668 <field name="mTabstrip">
2669 document.getAnonymousElementByAttribute(this, "anonid", "arrowscrollbox");
2672 <field name="_prefObserver"><![CDATA[({
2675 observe: function (subject, topic, data) {
2677 case "browser.tabs.closeButtons":
2678 this.tabContainer.mCloseButtons = Services.prefs.getIntPref(data);
2679 this.tabContainer.adjustTabstrip();
2681 case "browser.tabs.autoHide":
2682 this.tabContainer.updateVisibility();
2684 case "browser.tabs.closeWindowWithLastTab":
2685 this.tabContainer._closeWindowWithLastTab = Services.prefs.getBoolPref(data);
2686 this.tabContainer.adjustTabstrip();
2691 <field name="_blockDblClick">false</field>
2693 <field name="_tabDropIndicator">
2694 document.getAnonymousElementByAttribute(this, "anonid", "tab-drop-indicator");
2697 <field name="_dragOverDelay">350</field>
2698 <field name="_dragTime">0</field>
2700 <field name="_container" readonly="true"><![CDATA[
2701 this.parentNode && this.parentNode.localName == "toolbar" ? this.parentNode : this;
2704 <property name="visible"
2705 onget="return !this._container.collapsed;">
2707 if (val == this.visible)
2710 this._container.collapsed = !val;
2713 this.tabbrowser.enterTabbedMode();
2715 document.getElementById("menu_closeWindow").hidden = !val;
2716 document.getElementById("menu_close").setAttribute("label",
2717 this.tabbrowser.mStringBundle.getString(val ? "tabs.closeTab" : "tabs.close"));
2719 if (window.TabsInTitlebar)
2720 TabsInTitlebar.allowedBy("tabs-visible", val);
2726 <method name="updateVisibility">
2728 if (this.childNodes.length - this.tabbrowser._removingTabs.length == 1 &&
2729 window.toolbar.visible)
2730 this.visible = !Services.prefs.getBoolPref("browser.tabs.autoHide");
2732 this.visible = true;
2736 <method name="adjustTabstrip">
2738 // modes for tabstrip
2739 // 0 - button on active tab only
2740 // 1 - close buttons on all tabs
2741 // 2 - no close buttons at all
2742 // 3 - close button at the end of the tabstrip
2743 switch (this.mCloseButtons) {
2745 if (this.childNodes.length == 1 && this._closeWindowWithLastTab)
2746 this.setAttribute("closebuttons", "hidden");
2748 this.setAttribute("closebuttons", "activetab");
2751 if (this.childNodes.length == 1) {
2752 if (this._closeWindowWithLastTab)
2753 this.setAttribute("closebuttons", "hidden");
2755 this.setAttribute("closebuttons", "alltabs");
2757 let tab = this.tabbrowser.visibleTabs[this.tabbrowser._numPinnedTabs];
2758 if (tab && tab.getBoundingClientRect().width > this.mTabClipWidth)
2759 this.setAttribute("closebuttons", "alltabs");
2761 this.setAttribute("closebuttons", "activetab");
2766 this.setAttribute("closebuttons", "never");
2769 var tabstripClosebutton = document.getElementById("tabs-closebutton");
2770 if (tabstripClosebutton && tabstripClosebutton.parentNode == this._container)
2771 tabstripClosebutton.collapsed = this.mCloseButtons != 3;
2775 <method name="_handleTabSelect">
2777 this.mTabstrip.ensureElementIsVisible(this.selectedItem);
2781 <method name="_fillTrailingGap">
2784 // if we're at the right side (and not the logical end,
2785 // which is why this works for both LTR and RTL)
2786 // of the tabstrip, we need to ensure that we stay
2787 // completely scrolled to the right side
2788 var tabStrip = this.mTabstrip;
2789 if (tabStrip.scrollPosition + tabStrip.scrollClientSize >
2790 tabStrip.scrollSize)
2791 tabStrip.scrollByPixels(-1);
2796 <method name="_positionPinnedTabs">
2798 var numPinned = this.tabbrowser._numPinnedTabs;
2799 var pinnedOnly = (numPinned == this.tabbrowser.visibleTabs.length);
2802 this.setAttribute("pinnedonly", "true");
2804 this.removeAttribute("pinnedonly");
2806 var scrollButtonWidth = (this.getAttribute("overflow") != "true" || pinnedOnly) ? 0 :
2807 this.mTabstrip._scrollButtonDown.scrollWidth;
2808 var paddingStart = this.mTabstrip.scrollboxPaddingStart;
2811 for (var i = numPinned - 1; i >= 0; i--) {
2812 let tab = this.childNodes[i];
2813 width += pinnedOnly ? 0 : tab.scrollWidth;
2814 if (this.getAttribute("overflow") != "true")
2815 tab.style.MozMarginStart = - (width + scrollButtonWidth) + "px";
2817 tab.style.MozMarginStart = - (width + scrollButtonWidth + paddingStart) + "px";
2819 if (width == 0 || this.getAttribute("overflow") != "true")
2820 this.style.MozMarginStart = width + "px";
2822 this.style.MozMarginStart = width + paddingStart + "px";
2823 this.mTabstrip.ensureElementIsVisible(this.selectedItem, false);
2827 <method name="handleEvent">
2828 <parameter name="aEvent"/>
2830 switch (aEvent.type) {
2832 if (aEvent.target != window)
2834 var width = this.mTabstrip.boxObject.width;
2835 if (width != this.mTabstripWidth) {
2836 this.adjustTabstrip();
2837 this._fillTrailingGap();
2838 this._handleTabSelect();
2839 this.mTabstripWidth = width;
2846 <field name="_animateElement">
2847 this.mTabstrip._scrollButtonDown;
2850 <method name="_notifyBackgroundTab">
2851 <parameter name="aTab"/>
2856 var scrollRect = this.mTabstrip.scrollClientRect;
2857 var tab = aTab.getBoundingClientRect();
2859 // Is the new tab already completely visible?
2860 if (scrollRect.left <= tab.left && tab.right <= scrollRect.right)
2863 if (this.mTabstrip.smoothScroll) {
2864 let selected = !this.selectedItem.pinned &&
2865 this.selectedItem.getBoundingClientRect();
2867 // Can we make both the new tab and the selected tab completely visible?
2869 Math.max(tab.right - selected.left, selected.right - tab.left) <=
2871 this.mTabstrip.ensureElementIsVisible(aTab);
2875 this.mTabstrip._smoothScrollByPixels(this.mTabstrip._isRTLScrollbox ?
2876 selected.right - scrollRect.right :
2877 selected.left - scrollRect.left);
2880 if (!this._animateElement.hasAttribute("notifybgtab")) {
2881 this._animateElement.setAttribute("notifybgtab", "true");
2882 setTimeout(function (ele) {
2883 ele.removeAttribute("notifybgtab");
2884 }, 150, this._animateElement);
2889 <method name="_getDragTargetTab">
2890 <parameter name="event"/>
2892 let tab = event.target.localName == "tab" ? event.target : null;
2894 (event.type == "drop" || event.type == "dragover") &&
2895 event.dataTransfer.dropEffect == "link") {
2896 let boxObject = tab.boxObject;
2897 if (event.screenX < boxObject.screenX + boxObject.width * .25 ||
2898 event.screenX > boxObject.screenX + boxObject.width * .75)
2905 <method name="_getDropIndex">
2906 <parameter name="event"/>
2908 var tabs = this.childNodes;
2909 var tab = this._getDragTargetTab(event);
2910 if (window.getComputedStyle(this, null).direction == "ltr") {
2911 for (let i = tab ? tab._tPos : 0; i < tabs.length; i++)
2912 if (event.screenX < tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2)
2915 for (let i = tab ? tab._tPos : 0; i < tabs.length; i++)
2916 if (event.screenX > tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2)
2923 <method name="_setEffectAllowedForDataTransfer">
2924 <parameter name="event"/>
2926 var dt = event.dataTransfer;
2927 // Disallow dropping multiple items
2928 if (dt.mozItemCount > 1)
2929 return dt.effectAllowed = "none";
2931 var types = dt.mozTypesAt(0);
2932 var sourceNode = null;
2933 // tabs are always added as the first type
2934 if (types[0] == TAB_DROP_TYPE) {
2935 var sourceNode = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
2936 if (sourceNode instanceof XULElement &&
2937 sourceNode.localName == "tab" &&
2938 (sourceNode.parentNode == this ||
2939 (sourceNode.ownerDocument.defaultView instanceof ChromeWindow &&
2940 sourceNode.ownerDocument.documentElement.getAttribute("windowtype") == "navigator:browser"))) {
2941 if (sourceNode.parentNode == this &&
2942 (event.screenX >= sourceNode.boxObject.screenX &&
2943 event.screenX <= (sourceNode.boxObject.screenX +
2944 sourceNode.boxObject.width))) {
2945 return dt.effectAllowed = "none";
2948 return dt.effectAllowed = "copyMove";
2952 if (browserDragAndDrop.canDropLink(event)) {
2953 // Here we need to do this manually
2954 return dt.effectAllowed = dt.dropEffect = "link";
2956 return dt.effectAllowed = "none";
2960 <method name="_continueScroll">
2961 <parameter name="event"/>
2963 // Workaround for bug 481904: Dragging a tab stops scrolling at
2964 // the tab's position when dragging to the first/last tab and back.
2965 var t = this.selectedItem;
2966 if (event.screenX >= t.boxObject.screenX &&
2967 event.screenX <= t.boxObject.screenX + t.boxObject.width &&
2968 event.screenY >= t.boxObject.screenY &&
2969 event.screenY <= t.boxObject.screenY + t.boxObject.height)
2970 this.mTabstrip.ensureElementIsVisible(t);
2974 <method name="_handleNewTab">
2975 <parameter name="tab"/>
2977 if (tab.parentNode != this)
2980 this.adjustTabstrip();
2982 if (tab.getAttribute("selected") == "true") {
2983 this._fillTrailingGap();
2984 this._handleTabSelect();
2986 this._notifyBackgroundTab(tab);
2989 // XXXmano: this is a temporary workaround for bug 345399
2990 // We need to manually update the scroll buttons disabled state
2991 // if a tab was inserted to the overflow area or removed from it
2992 // without any scrolling and when the tabbar has already
2994 this.mTabstrip._updateScrollButtonsDisabledState();
2998 <method name="_canAdvanceToTab">
2999 <parameter name="aTab"/>
3002 return this.tabbrowser._removingTabs.indexOf(aTab) == -1;
3007 <!-- Deprecated stuff, implemented for backwards compatibility. -->
3008 <property name="mTabstripClosebutton" readonly="true"
3009 onget="return document.getElementById('tabs-closebutton');"/>
3010 <property name="mAllTabsPopup" readonly="true"
3011 onget="return document.getElementById('alltabs-popup');"/>
3015 <handler event="TabSelect" action="this._handleTabSelect();"/>
3017 <handler event="transitionend"><![CDATA[
3018 if (event.propertyName != "max-width")
3021 var tab = event.target;
3023 if (tab.getAttribute("fadein") == "true")
3024 this._handleNewTab(tab);
3025 else if (this.tabbrowser._removingTabs.indexOf(tab) > -1)
3026 this.tabbrowser._endRemoveTab(tab);
3029 <handler event="dblclick"><![CDATA[
3030 // See hack note in the tabbrowser-close-button binding
3031 if (!this._blockDblClick && event.button == 0 &&
3032 event.originalTarget.localName == "box")
3036 <handler event="click"><![CDATA[
3037 if (event.button != 1)
3040 if (event.target.localName == "tab") {
3041 if (this.childNodes.length > 1 || !this._closeWindowWithLastTab)
3042 this.tabbrowser.removeTab(event.target, {animate: true});
3043 } else if (event.originalTarget.localName == "box") {
3049 event.stopPropagation();
3052 <handler event="keypress"><![CDATA[
3053 if (event.altKey || event.shiftKey ||
3057 !event.ctrlKey || event.metaKey)
3061 switch (event.keyCode) {
3062 case KeyEvent.DOM_VK_UP:
3063 this.tabbrowser.moveTabBackward();
3065 case KeyEvent.DOM_VK_DOWN:
3066 this.tabbrowser.moveTabForward();
3068 case KeyEvent.DOM_VK_RIGHT:
3069 case KeyEvent.DOM_VK_LEFT:
3070 this.tabbrowser.moveTabOver(event);
3072 case KeyEvent.DOM_VK_HOME:
3073 this.tabbrowser.moveTabToStart();
3075 case KeyEvent.DOM_VK_END:
3076 this.tabbrowser.moveTabToEnd();
3079 // Stop the keypress event for the above keyboard
3083 event.stopPropagation();
3084 event.preventDefault();
3087 <handler event="dragstart"><![CDATA[
3088 var tab = this._getDragTargetTab(event);
3092 let dt = event.dataTransfer;
3093 dt.mozSetDataAt(TAB_DROP_TYPE, tab, 0);
3094 let uri = this.tabbrowser.getBrowserForTab(tab).currentURI;
3095 let spec = uri ? uri.spec : "about:blank";
3097 // We must not set text/x-moz-url or text/plain data here,
3098 // otherwise trying to deatch the tab by dropping it on the desktop
3099 // may result in an "internet shortcut"
3100 dt.mozSetDataAt("text/x-moz-text-internal", spec, 0);
3102 // Set the cursor to an arrow during tab drags.
3103 dt.mozCursor = "default";
3105 let canvas = tabPreviews.capture(tab, false);
3106 dt.setDragImage(canvas, 0, 0);
3107 event.stopPropagation();
3110 <handler event="dragover"><![CDATA[
3111 var effects = this._setEffectAllowedForDataTransfer(event);
3113 var ind = this._tabDropIndicator;
3114 if (effects == "" || effects == "none") {
3115 ind.collapsed = true;
3116 this._continueScroll(event);
3119 event.preventDefault();
3120 event.stopPropagation();
3122 var tabStrip = this.mTabstrip;
3123 var ltr = (window.getComputedStyle(this, null).direction == "ltr");
3125 // autoscroll the tab strip if we drag over the scroll
3126 // buttons, even if we aren't dragging a tab, but then
3127 // return to avoid drawing the drop indicator
3128 var pixelsToScroll = 0;
3129 if (this.getAttribute("overflow") == "true") {
3130 var targetAnonid = event.originalTarget.getAttribute("anonid");
3131 switch (targetAnonid) {
3132 case "scrollbutton-up":
3133 pixelsToScroll = tabStrip.scrollIncrement * -1;
3135 case "scrollbutton-down":
3136 pixelsToScroll = tabStrip.scrollIncrement;
3140 tabStrip.scrollByPixels((ltr ? 1 : -1) * pixelsToScroll);
3143 if (effects == "link") {
3144 let tab = this._getDragTargetTab(event);
3146 if (!this._dragTime)
3147 this._dragTime = Date.now();
3148 if (Date.now() >= this._dragTime + this._dragOverDelay)
3149 this.selectedItem = tab;
3150 ind.collapsed = true;
3155 var newIndex = this._getDropIndex(event);
3156 var scrollRect = tabStrip.scrollClientRect;
3157 var rect = this.getBoundingClientRect();
3158 var minMargin = scrollRect.left - rect.left;
3159 var maxMargin = Math.min(minMargin + scrollRect.width,
3162 [minMargin, maxMargin] = [this.clientWidth - maxMargin,
3163 this.clientWidth - minMargin];
3165 if (pixelsToScroll) {
3166 // if we are scrolling, put the drop indicator at the edge
3167 // so that it doesn't jump while scrolling
3168 newMargin = (pixelsToScroll > 0) ? maxMargin : minMargin;
3171 if (newIndex == this.childNodes.length) {
3172 let tabRect = this.childNodes[newIndex-1].getBoundingClientRect();
3174 newMargin = tabRect.right - rect.left;
3176 newMargin = rect.right - tabRect.left;
3179 let tabRect = this.childNodes[newIndex].getBoundingClientRect();
3181 newMargin = tabRect.left - rect.left;
3183 newMargin = rect.right - tabRect.right;
3187 ind.collapsed = false;
3189 newMargin += ind.clientWidth / 2;
3193 ind.style.MozTransform = "translate(" + Math.round(newMargin) + "px)";
3194 ind.style.MozMarginStart = (-ind.clientWidth) + "px";
3195 ind.style.marginTop = (-ind.clientHeight) + "px";
3198 <handler event="drop"><![CDATA[
3199 var dt = event.dataTransfer;
3200 var dropEffect = dt.dropEffect;
3202 if (dropEffect != "link") { // copy or move
3203 draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
3204 // not our drop then
3209 this._tabDropIndicator.collapsed = true;
3210 event.stopPropagation();
3212 if (draggedTab && (dropEffect == "copy" ||
3213 draggedTab.parentNode == this)) {
3214 let newIndex = this._getDropIndex(event);
3215 if (dropEffect == "copy") {
3216 // copy the dropped tab (wherever it's from)
3217 let newTab = this.tabbrowser.duplicateTab(draggedTab);
3218 this.tabbrowser.moveTabTo(newTab, newIndex);
3219 if (draggedTab.parentNode != this || event.shiftKey)
3220 this.selectedItem = newTab;
3222 // move the dropped tab
3223 if (newIndex > draggedTab._tPos)
3226 if (draggedTab.pinned) {
3227 if (newIndex >= this.tabbrowser._numPinnedTabs)
3228 this.tabbrowser.unpinTab(draggedTab);
3230 if (newIndex <= this.tabbrowser._numPinnedTabs - 1)
3231 this.tabbrowser.pinTab(draggedTab);
3234 this.tabbrowser.moveTabTo(draggedTab, newIndex);
3236 } else if (draggedTab) {
3237 // swap the dropped tab with a new one we create and then close
3238 // it in the other window (making it seem to have moved between
3240 let newIndex = this._getDropIndex(event);
3241 let newTab = this.tabbrowser.addTab("about:blank");
3242 let newBrowser = this.tabbrowser.getBrowserForTab(newTab);
3243 // Stop the about:blank load
3245 // make sure it has a docshell
3246 newBrowser.docShell;
3248 this.tabbrowser.moveTabTo(newTab, newIndex);
3250 this.tabbrowser.swapBrowsersAndCloseOther(newTab, draggedTab);
3252 // We need to select the tab after we've done
3253 // swapBrowsersAndCloseOther, so that the updateCurrentBrowser
3254 // it triggers will correctly update our URL bar.
3255 this.tabbrowser.selectedTab = newTab;
3257 let url = browserDragAndDrop.drop(event, { });
3259 // valid urls don't contain spaces ' '; if we have a space it isn't a valid url.
3260 // Also disallow dropping javascript: or data: urls--bail out
3261 if (!url || !url.length || url.indexOf(" ", 0) != -1 ||
3262 /^\s*(javascript|data):/.test(url))
3265 let bgLoad = Services.prefs.getBoolPref("browser.tabs.loadInBackground");
3270 let tab = this._getDragTargetTab(event);
3271 if (!tab || dropEffect == "copy") {
3272 // We're adding a new tab.
3273 let newIndex = this._getDropIndex(event);
3274 let newTab = this.tabbrowser.loadOneTab(getShortcutOrURI(url), {inBackground: bgLoad});
3275 this.tabbrowser.moveTabTo(newTab, newIndex);
3277 // Load in an existing tab.
3279 this.tabbrowser.getBrowserForTab(tab).loadURI(getShortcutOrURI(url));
3281 this.selectedItem = tab;
3283 // Just ignore invalid urls
3289 <handler event="dragend"><![CDATA[
3290 // Note: while this case is correctly handled here, this event
3291 // isn't dispatched when the tab is moved within the tabstrip,
3294 // * mozUserCancelled = the user pressed ESC to cancel the drag
3295 var dt = event.dataTransfer;
3296 if (dt.mozUserCancelled || dt.dropEffect != "none")
3299 // Disable detach within the browser toolbox
3300 var eX = event.screenX;
3301 var wX = window.screenX;
3302 // check if the drop point is horizontally within the window
3303 if (eX > wX && eX < (wX + window.outerWidth)) {
3304 let bo = this.mTabstrip.boxObject;
3305 // also avoid detaching if the the tab was dropped too close to
3306 // the tabbar (half a tab)
3307 let endScreenY = bo.screenY + 1.5 * bo.height;
3308 let eY = event.screenY;
3309 if (eY < endScreenY && eY > window.screenY)
3313 var draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
3314 this.tabbrowser.replaceTabWithWindow(draggedTab);
3315 event.stopPropagation();
3318 <handler event="dragexit"><![CDATA[
3321 // This does not work at all (see bug 458613)
3322 var target = event.relatedTarget;
3323 while (target && target != this)
3324 target = target.parentNode;
3328 this._tabDropIndicator.collapsed = true;
3329 this._continueScroll(event);
3330 event.stopPropagation();
3335 <!-- close-tab-button binding
3336 This binding relies on the structure of the tabbrowser binding.
3337 Therefore it should only be used as a child of the tab or the tabs
3338 element (in both cases, when they are anonymous nodes of <tabbrowser>).
3340 <binding id="tabbrowser-close-tab-button"
3341 extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-image">
3343 <handler event="click" button="0"><![CDATA[
3344 var bindingParent = document.getBindingParent(this);
3345 var tabContainer = bindingParent.parentNode;
3346 /* The only sequence in which a second click event (i.e. dblclik)
3347 * can be dispatched on an in-tab close button is when it is shown
3348 * after the first click (i.e. the first click event was dispatched
3349 * on the tab). This happens when we show the close button only on
3350 * the active tab. (bug 352021)
3351 * The only sequence in which a third click event can be dispatched
3352 * on an in-tab close button is when the tab was opened with a
3353 * double click on the tabbar. (bug 378344)
3354 * In both cases, it is most likely that the close button area has
3355 * been accidentally clicked, therefore we do not close the tab.
3357 * We don't want to ignore processing of more than one click event,
3358 * though, since the user might actually be repeatedly clicking to
3359 * close many tabs at once.
3361 if (event.detail > 1 && !this._ignoredClick) {
3362 this._ignoredClick = true;
3366 // Reset the "ignored click" flag
3367 this._ignoredClick = false;
3369 tabContainer.tabbrowser.removeTab(bindingParent, {animate: true});
3370 tabContainer._blockDblClick = true;
3372 /* XXXmano hack (see bug 343628):
3373 * Since we're removing the event target, if the user
3374 * double-clicks this button, the dblclick event will be dispatched
3375 * with the tabbar as its event target (and explicit/originalTarget),
3376 * which treats that as a mouse gesture for opening a new tab.
3377 * In this context, we're manually blocking the dblclick event
3378 * (see dblclick handler).
3380 var clickedOnce = false;
3381 function enableDblClick(event) {
3382 if (event.detail == 1 && !clickedOnce) {
3386 setTimeout(function() {
3387 tabContainer._blockDblClick = false;
3389 tabContainer.removeEventListener("click", enableDblClick, false);
3391 tabContainer.addEventListener("click", enableDblClick, false);
3394 <handler event="dblclick" button="0" phase="capturing">
3395 // for the one-close-button case
3396 event.stopPropagation();
3399 <handler event="dragstart">
3400 event.stopPropagation();
3405 <binding id="tabbrowser-tab" display="xul:hbox"
3406 extends="chrome://global/content/bindings/tabbox.xml#tab">
3408 <stylesheet src="chrome://browser/content/tabbrowser.css"/>
3411 <content context="tabContextMenu" closetabtext="&closeTab.label;">
3412 <xul:stack class="tab-stack" flex="1">
3413 <xul:hbox xbl:inherits="pinned,selected,titlechanged"
3414 class="tab-background">
3415 <xul:hbox xbl:inherits="pinned,selected,titlechanged"
3416 class="tab-background-start"/>
3417 <xul:hbox xbl:inherits="pinned,selected,titlechanged"
3418 class="tab-background-middle"/>
3419 <xul:hbox xbl:inherits="pinned,selected,titlechanged"
3420 class="tab-background-end"/>
3422 <xul:hbox xbl:inherits="pinned,selected,titlechanged"
3423 class="tab-content" align="center">
3424 <xul:image xbl:inherits="fadein,pinned,busy,progress,selected"
3425 class="tab-throbber"
3426 role="presentation"/>
3427 <xul:image xbl:inherits="validate,src=image,fadein,pinned,selected"
3428 class="tab-icon-image"
3429 role="presentation"/>
3431 xbl:inherits="value=label,crop,accesskey,fadein,pinned,selected"
3432 class="tab-text tab-label"
3433 role="presentation"/>
3434 <xul:toolbarbutton anonid="close-button"
3435 xbl:inherits="fadein,pinned,selected"
3437 clickthrough="never"
3438 class="tab-close-button"/>
3444 <property name="pinned" readonly="true">
3446 return this.getAttribute("pinned") == "true";
3449 <property name="hidden" readonly="true">
3451 return this.getAttribute("hidden") == "true";
3455 <field name="mOverCloseButton">false</field>
3456 <field name="mCorrespondingMenuitem">null</field>
3460 <handler event="mouseover">
3461 var anonid = event.originalTarget.getAttribute("anonid");
3462 if (anonid == "close-button")
3463 this.mOverCloseButton = true;
3465 <handler event="mouseout">
3466 var anonid = event.originalTarget.getAttribute("anonid");
3467 if (anonid == "close-button")
3468 this.mOverCloseButton = false;
3470 <handler event="dragstart" phase="capturing">
3471 this.style.MozUserFocus = '';
3473 <handler event="mousedown" button="0" phase="capturing">
3475 if (this.mOverCloseButton) {
3476 event.stopPropagation();
3479 this.style.MozUserFocus = 'ignore';
3480 this.clientTop; // just using this to flush style updates
3484 <handler event="mousedown" button="1">
3485 this.style.MozUserFocus = 'ignore';
3488 <handler event="mousedown" button="2">
3489 this.style.MozUserFocus = 'ignore';
3492 <handler event="mouseup">
3493 this.style.MozUserFocus = '';
3498 <binding id="tabbrowser-alltabs-popup"
3499 extends="chrome://global/content/bindings/popup.xml#popup">
3500 <implementation implements="nsIDOMEventListener">
3501 <method name="_menuItemOnCommand">
3502 <parameter name="aEvent"/>
3504 gBrowser.selectedTab = aEvent.target.tab;
3508 <method name="_tabOnAttrModified">
3509 <parameter name="aEvent"/>
3511 var tab = aEvent.target;
3512 this._setMenuitemAttributes(tab.mCorrespondingMenuitem, tab);
3516 <method name="_tabOnTabClose">
3517 <parameter name="aEvent"/>
3519 var menuItem = aEvent.target.mCorrespondingMenuitem;
3521 this.removeChild(menuItem);
3525 <method name="handleEvent">
3526 <parameter name="aEvent"/>
3528 if (!aEvent.isTrusted)
3531 switch (aEvent.type) {
3533 this._menuItemOnCommand(aEvent);
3535 case "TabAttrModified":
3536 this._tabOnAttrModified(aEvent);
3539 this._tabOnTabClose(aEvent);
3542 this._createTabMenuItem(aEvent.originalTarget);
3545 this._updateTabsVisibilityStatus();
3551 <method name="_updateTabsVisibilityStatus">
3553 var tabContainer = gBrowser.tabContainer;
3554 // We don't want menu item decoration unless there is overflow.
3555 if (tabContainer.getAttribute("overflow") != "true")
3558 var tabstripBO = tabContainer.mTabstrip.scrollBoxObject;
3559 for (var i = 0; i < this.childNodes.length; i++) {
3560 var curTabBO = this.childNodes[i].tab.boxObject;
3561 if (curTabBO.screenX >= tabstripBO.screenX &&
3562 curTabBO.screenX + curTabBO.width <= tabstripBO.screenX + tabstripBO.width)
3563 this.childNodes[i].setAttribute("tabIsVisible", "true");
3565 this.childNodes[i].removeAttribute("tabIsVisible");
3570 <method name="_createTabMenuItem">
3571 <parameter name="aTab"/>
3573 var menuItem = document.createElementNS(
3574 "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
3577 menuItem.setAttribute("class", "menuitem-iconic alltabs-item menuitem-with-favicon");
3579 this._setMenuitemAttributes(menuItem, aTab);
3581 // Keep some attributes of the menuitem in sync with its
3582 // corresponding tab (e.g. the tab label)
3583 aTab.mCorrespondingMenuitem = menuItem;
3584 menuItem.tab = aTab;
3585 menuItem.addEventListener("command", this, false);
3587 this.appendChild(menuItem);
3592 <method name="_setMenuitemAttributes">
3593 <parameter name="aMenuitem"/>
3594 <parameter name="aTab"/>
3596 aMenuitem.setAttribute("label", aTab.label);
3597 aMenuitem.setAttribute("crop", aTab.getAttribute("crop"));
3598 aMenuitem.setAttribute("image", aTab.getAttribute("image"));
3600 if (aTab.hasAttribute("busy"))
3601 aMenuitem.setAttribute("busy", aTab.getAttribute("busy"));
3603 aMenuitem.removeAttribute("busy");
3606 aMenuitem.setAttribute("selected", "true");
3608 aMenuitem.removeAttribute("selected");
3614 <handler event="popupshowing">
3616 // set up the menu popup
3617 var tabcontainer = gBrowser.tabContainer;
3618 let tabs = gBrowser.visibleTabs;
3620 // Listen for changes in the tab bar.
3621 tabcontainer.addEventListener("TabOpen", this, false);
3622 tabcontainer.addEventListener("TabAttrModified", this, false);
3623 tabcontainer.addEventListener("TabClose", this, false);
3624 tabcontainer.mTabstrip.addEventListener("scroll", this, false);
3626 for (var i = 0; i < tabs.length; i++) {
3627 this._createTabMenuItem(tabs[i]);
3629 this._updateTabsVisibilityStatus();
3632 <handler event="popuphidden">
3634 // clear out the menu popup and remove the listeners
3635 while (this.hasChildNodes()) {
3636 var menuItem = this.lastChild;
3637 menuItem.removeEventListener("command", this, false);
3638 menuItem.tab.mCorrespondingMenuitem = null;
3639 this.removeChild(menuItem);
3641 var tabcontainer = gBrowser.tabContainer;
3642 tabcontainer.mTabstrip.removeEventListener("scroll", this, false);
3643 tabcontainer.removeEventListener("TabOpen", this, false);
3644 tabcontainer.removeEventListener("TabAttrModified", this, false);
3645 tabcontainer.removeEventListener("TabClose", this, false);
3648 <handler event="DOMMenuItemActive">
3650 var tab = event.target.tab;
3652 let overLink = tab.linkedBrowser.currentURI.spec;
3653 if (overLink == "about:blank")
3655 XULBrowserWindow.setOverLink(overLink, null);
3659 <handler event="DOMMenuItemInactive">
3661 XULBrowserWindow.setOverLink("", null);