3 <!-- This Source Code Form is subject to the terms of the Mozilla Public
4 - License, v. 2.0. If a copy of the MPL was not distributed with this
5 - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
8 <!ENTITY % tabBrowserDTD SYSTEM "chrome://browser/locale/tabbrowser.dtd" >
12 # MAKE_E10S_WORK surrounds code needed to have the front-end try to be smart
13 # about using non-remote browsers for loading certain URIs when remote tabs
14 # (browser.tabs.remote) are enabled.
15 #define MAKE_E10S_WORK 1
17 <bindings id="tabBrowserBindings"
18 xmlns="http://www.mozilla.org/xbl"
19 xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
20 xmlns:xbl="http://www.mozilla.org/xbl">
22 <binding id="tabbrowser">
24 <stylesheet src="chrome://browser/content/tabbrowser.css"/>
28 <xul:stringbundle anonid="tbstringbundle" src="chrome://browser/locale/tabbrowser.properties"/>
29 <xul:tabbox anonid="tabbox" class="tabbrowser-tabbox"
30 flex="1" eventnode="document" xbl:inherits="handleCtrlPageUpDown"
31 onselect="if (event.target.localName == 'tabpanels') this.parentNode.updateCurrentBrowser();">
32 <xul:tabpanels flex="1" class="plain" selectedIndex="0" anonid="panelcontainer">
33 <xul:notificationbox flex="1">
34 <xul:hbox flex="1" class="browserSidebarContainer">
35 <xul:vbox flex="1" class="browserContainer">
36 <xul:stack flex="1" class="browserStack" anonid="browserStack">
37 <xul:browser anonid="initialBrowser" type="content-primary" message="true" messagemanagergroup="browsers"
38 xbl:inherits="tooltip=contenttooltip,contextmenu=contentcontextmenu,autocompletepopup,selectpopup"/>
42 </xul:notificationbox>
47 <implementation implements="nsIDOMEventListener, nsIMessageListener">
49 <property name="tabContextMenu" readonly="true"
50 onget="return this.tabContainer.contextMenu;"/>
52 <field name="tabContainer" readonly="true">
53 document.getElementById(this.getAttribute("tabcontainer"));
55 <field name="tabs" readonly="true">
56 this.tabContainer.childNodes;
59 <property name="visibleTabs" readonly="true">
61 if (!this._visibleTabs)
62 this._visibleTabs = Array.filter(this.tabs,
63 function (tab) !tab.hidden && !tab.closing);
64 return this._visibleTabs;
68 <field name="closingTabsEnum" readonly="true">({ ALL: 0, OTHER: 1, TO_END: 2 });</field>
70 <field name="_visibleTabs">null</field>
72 <field name="mURIFixup" readonly="true">
73 Components.classes["@mozilla.org/docshell/urifixup;1"]
74 .getService(Components.interfaces.nsIURIFixup);
76 <field name="mFaviconService" readonly="true">
77 Components.classes["@mozilla.org/browser/favicon-service;1"]
78 .getService(Components.interfaces.nsIFaviconService);
80 <field name="_placesAutocomplete" readonly="true">
81 Components.classes["@mozilla.org/autocomplete/search;1?name=history"]
82 .getService(Components.interfaces.mozIPlacesAutoComplete);
84 <field name="_unifiedComplete" readonly="true">
85 Components.classes["@mozilla.org/autocomplete/search;1?name=unifiedcomplete"]
86 .getService(Components.interfaces.mozIPlacesAutoComplete);
88 <field name="PlacesUtils" readonly="true">
89 (Components.utils.import("resource://gre/modules/PlacesUtils.jsm", {})).PlacesUtils;
91 <field name="mTabBox" readonly="true">
92 document.getAnonymousElementByAttribute(this, "anonid", "tabbox");
94 <field name="mPanelContainer" readonly="true">
95 document.getAnonymousElementByAttribute(this, "anonid", "panelcontainer");
97 <field name="mStringBundle">
98 document.getAnonymousElementByAttribute(this, "anonid", "tbstringbundle");
100 <field name="mCurrentTab">
103 <field name="_lastRelatedTab">
106 <field name="mCurrentBrowser">
109 <field name="mProgressListeners">
112 <field name="mTabsProgressListeners">
115 <field name="mTabListeners">
118 <field name="mTabFilters">
121 <field name="mIsBusy">
124 <field name="arrowKeysShouldWrap" readonly="true">
132 <field name="_autoScrollPopup">
136 <field name="_previewMode">
140 <field name="_lastFindValue">
144 <property name="_numPinnedTabs" readonly="true">
146 for (var i = 0; i < this.tabs.length; i++) {
147 if (!this.tabs[i].pinned)
154 <property name="formValidationAnchor" readonly="true">
156 if (this.mCurrentTab._formValidationAnchor) {
157 return this.mCurrentTab._formValidationAnchor;
159 let stack = this.mCurrentBrowser.parentNode;
160 // Create an anchor for the form validation popup
161 const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
162 let formValidationAnchor = document.createElementNS(NS_XUL, "hbox");
163 formValidationAnchor.className = "form-validation-anchor";
164 formValidationAnchor.hidden = true;
165 stack.appendChild(formValidationAnchor);
166 return this.mCurrentTab._formValidationAnchor = formValidationAnchor;
170 <method name="isFindBarInitialized">
171 <parameter name="aTab"/>
173 return (aTab || this.selectedTab)._findBar != undefined;
177 <method name="getFindBar">
178 <parameter name="aTab"/>
181 aTab = this.selectedTab;
184 return aTab._findBar;
186 let findBar = document.createElementNS(this.namespaceURI, "findbar");
187 let browser = this.getBrowserForTab(aTab);
188 let browserContainer = this.getBrowserContainer(browser);
189 browserContainer.appendChild(findBar);
191 // Force a style flush to ensure that our binding is attached.
194 findBar.browser = browser;
195 findBar._findField.value = this._lastFindValue;
197 aTab._findBar = findBar;
199 let event = document.createEvent("Events");
200 event.initEvent("TabFindInitialized", true, false);
201 aTab.dispatchEvent(event);
207 <method name="getStatusPanel">
209 if (!this._statusPanel) {
210 this._statusPanel = document.createElementNS(this.namespaceURI, "statuspanel");
211 this._statusPanel.setAttribute("inactive", "true");
212 this._statusPanel.setAttribute("layer", "true");
213 this._appendStatusPanel();
215 return this._statusPanel;
219 <method name="_appendStatusPanel">
221 if (this._statusPanel) {
222 let browser = this.selectedBrowser;
223 let browserContainer = this.getBrowserContainer(browser);
224 browserContainer.insertBefore(this._statusPanel, browser.parentNode.nextSibling);
229 <method name="updateWindowResizers">
231 if (!window.gShowPageResizers)
234 var show = window.windowState == window.STATE_NORMAL;
235 for (let i = 0; i < this.browsers.length; i++) {
236 this.browsers[i].showWindowResizer = show;
241 <method name="_setCloseKeyState">
242 <parameter name="aEnabled"/>
244 let keyClose = document.getElementById("key_close");
245 let closeKeyEnabled = keyClose.getAttribute("disabled") != "true";
246 if (closeKeyEnabled == aEnabled)
250 keyClose.removeAttribute("disabled");
252 keyClose.setAttribute("disabled", "true");
254 // We also want to remove the keyboard shortcut from the file menu
255 // when the shortcut is disabled, and bring it back when it's
258 // Fixing bug 630826 could make that happen automatically.
259 // Fixing bug 630830 could avoid the ugly hack below.
261 let closeMenuItem = document.getElementById("menu_close");
262 let parentPopup = closeMenuItem.parentNode;
263 let nextItem = closeMenuItem.nextSibling;
264 let clonedItem = closeMenuItem.cloneNode(true);
266 parentPopup.removeChild(closeMenuItem);
269 clonedItem.setAttribute("key", "key_close");
271 clonedItem.removeAttribute("key");
273 parentPopup.insertBefore(clonedItem, nextItem);
277 <method name="pinTab">
278 <parameter name="aTab"/>
286 this.moveTabTo(aTab, this._numPinnedTabs);
287 aTab.setAttribute("pinned", "true");
288 this.tabContainer._unlockTabSizing();
289 this.tabContainer._positionPinnedTabs();
290 this.tabContainer.adjustTabstrip();
292 // Bug 961867 - [e10s] Implement the logic for app tabs
293 if (!gMultiProcessBrowser)
294 this.getBrowserForTab(aTab).docShell.isAppTab = true;
297 this._setCloseKeyState(false);
299 let event = document.createEvent("Events");
300 event.initEvent("TabPinned", true, false);
301 aTab.dispatchEvent(event);
305 <method name="unpinTab">
306 <parameter name="aTab"/>
311 this.moveTabTo(aTab, this._numPinnedTabs - 1);
312 aTab.removeAttribute("pinned");
313 aTab.style.MozMarginStart = "";
314 this.tabContainer._unlockTabSizing();
315 this.tabContainer._positionPinnedTabs();
316 this.tabContainer.adjustTabstrip();
318 // Bug 961867 - [e10s] Implement the logic for app tabs
319 if (!gMultiProcessBrowser)
320 this.getBrowserForTab(aTab).docShell.isAppTab = false;
323 this._setCloseKeyState(true);
325 let event = document.createEvent("Events");
326 event.initEvent("TabUnpinned", true, false);
327 aTab.dispatchEvent(event);
331 <method name="previewTab">
332 <parameter name="aTab"/>
333 <parameter name="aCallback"/>
336 let currentTab = this.selectedTab;
338 // Suppress focus, ownership and selected tab changes
339 this._previewMode = true;
340 this.selectedTab = aTab;
343 this.selectedTab = currentTab;
344 this._previewMode = false;
350 <method name="getBrowserAtIndex">
351 <parameter name="aIndex"/>
354 return this.browsers[aIndex];
359 <method name="getBrowserIndexForDocument">
360 <parameter name="aDocument"/>
363 var tab = this._getTabForContentWindow(aDocument.defaultView);
364 return tab ? tab._tPos : -1;
369 <method name="getBrowserForDocument">
370 <parameter name="aDocument"/>
373 var tab = this._getTabForContentWindow(aDocument.defaultView);
374 return tab ? tab.linkedBrowser : null;
379 <method name="_getTabForContentWindow">
380 <parameter name="aWindow"/>
383 // When not using remote browsers, we can take a fast path by getting
384 // directly from the content window to the browser without looping
385 // over all browsers.
386 if (!gMultiProcessBrowser) {
387 let browser = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
388 .getInterface(Ci.nsIWebNavigation)
389 .QueryInterface(Ci.nsIDocShell)
391 return this._getTabForBrowser(browser);
394 for (let i = 0; i < this.browsers.length; i++) {
395 if (this.browsers[i].contentWindow == aWindow)
403 <method name="_getTabForBrowser">
404 <parameter name="aBrowser"/>
407 for (let i = 0; i < this.tabs.length; i++) {
408 if (this.tabs[i].linkedBrowser == aBrowser)
416 <method name="getNotificationBox">
417 <parameter name="aBrowser"/>
420 return this.getSidebarContainer(aBrowser).parentNode;
425 <method name="getSidebarContainer">
426 <parameter name="aBrowser"/>
429 return this.getBrowserContainer(aBrowser).parentNode;
434 <method name="getBrowserContainer">
435 <parameter name="aBrowser"/>
438 return (aBrowser || this.mCurrentBrowser).parentNode.parentNode;
443 <method name="getTabModalPromptBox">
444 <parameter name="aBrowser"/>
447 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
448 let browser = (aBrowser || this.mCurrentBrowser);
449 let stack = browser.parentNode;
453 appendPrompt : function(args, onCloseCallback) {
454 let newPrompt = document.createElementNS(XUL_NS, "tabmodalprompt");
455 stack.appendChild(newPrompt);
456 browser.setAttribute("tabmodalPromptShowing", true);
458 newPrompt.clientTop; // style flush to assure binding is attached
460 let tab = self._getTabForBrowser(browser);
461 newPrompt.init(args, tab, onCloseCallback);
465 removePrompt : function(aPrompt) {
466 stack.removeChild(aPrompt);
468 let prompts = this.listPrompts();
469 if (prompts.length) {
470 let prompt = prompts[prompts.length - 1];
471 prompt.Dialog.setDefaultFocus();
473 browser.removeAttribute("tabmodalPromptShowing");
478 listPrompts : function(aPrompt) {
479 let els = stack.getElementsByTagNameNS(XUL_NS, "tabmodalprompt");
480 // NodeList --> real JS array
481 let prompts = Array.slice(els);
491 <method name="_callProgressListeners">
492 <parameter name="aBrowser"/>
493 <parameter name="aMethod"/>
494 <parameter name="aArguments"/>
495 <parameter name="aCallGlobalListeners"/>
496 <parameter name="aCallTabsListeners"/>
501 aBrowser = this.mCurrentBrowser;
503 if (aCallGlobalListeners != false &&
504 aBrowser == this.mCurrentBrowser) {
505 this.mProgressListeners.forEach(function (p) {
508 if (!p[aMethod].apply(p, aArguments))
511 // don't inhibit other listeners
512 Components.utils.reportError(e);
518 if (aCallTabsListeners != false) {
519 aArguments.unshift(aBrowser);
521 this.mTabsProgressListeners.forEach(function (p) {
524 if (!p[aMethod].apply(p, aArguments))
527 // don't inhibit other listeners
528 Components.utils.reportError(e);
538 <!-- A web progress listener object definition for a given tab. -->
539 <method name="mTabProgressListener">
540 <parameter name="aTab"/>
541 <parameter name="aBrowser"/>
542 <parameter name="aStartsBlank"/>
549 mBlank: aStartsBlank,
551 // cache flags for correct status UI update after tab switching
557 // count of open requests (should always be 0 or 1)
560 destroy: function () {
562 delete this.mBrowser;
563 delete this.mTabBrowser;
566 _callProgressListeners: function () {
567 Array.unshift(arguments, this.mBrowser);
568 return this.mTabBrowser._callProgressListeners.apply(this.mTabBrowser, arguments);
571 _shouldShowProgress: function (aRequest) {
575 if (gMultiProcessBrowser)
578 // Don't show progress indicators in tabs for about: URIs
579 // pointing to local resources.
581 let channel = aRequest.QueryInterface(Ci.nsIChannel);
582 if (channel.originalURI.schemeIs("about") &&
583 (channel.URI.schemeIs("jar") || channel.URI.schemeIs("file")))
590 onProgressChange: function (aWebProgress, aRequest,
591 aCurSelfProgress, aMaxSelfProgress,
592 aCurTotalProgress, aMaxTotalProgress) {
593 this.mTotalProgress = aMaxTotalProgress ? aCurTotalProgress / aMaxTotalProgress : 0;
595 if (!this._shouldShowProgress(aRequest))
598 if (this.mTotalProgress)
599 this.mTab.setAttribute("progress", "true");
601 this._callProgressListeners("onProgressChange",
602 [aWebProgress, aRequest,
603 aCurSelfProgress, aMaxSelfProgress,
604 aCurTotalProgress, aMaxTotalProgress]);
607 onProgressChange64: function (aWebProgress, aRequest,
608 aCurSelfProgress, aMaxSelfProgress,
609 aCurTotalProgress, aMaxTotalProgress) {
610 return this.onProgressChange(aWebProgress, aRequest,
611 aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress,
615 onStateChange: function (aWebProgress, aRequest, aStateFlags, aStatus) {
619 var oldBlank = this.mBlank;
621 const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
622 const nsIChannel = Components.interfaces.nsIChannel;
624 if (aStateFlags & nsIWebProgressListener.STATE_START) {
625 this.mRequestCount++;
627 else if (aStateFlags & nsIWebProgressListener.STATE_STOP) {
628 const NS_ERROR_UNKNOWN_HOST = 2152398878;
629 if (--this.mRequestCount > 0 && aStatus == NS_ERROR_UNKNOWN_HOST) {
630 // to prevent bug 235825: wait for the request handled
631 // by the automatic keyword resolver
634 // since we (try to) only handle STATE_STOP of the last request,
635 // the count of open requests should now be 0
636 this.mRequestCount = 0;
639 if (aStateFlags & nsIWebProgressListener.STATE_START &&
640 aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
641 // It's okay to clear what the user typed when we start
642 // loading a document. If the user types, this counter gets
643 // set to zero, if the document load ends without an
644 // onLocationChange, this counter gets decremented
645 // (so we keep it while switching tabs after failed loads)
646 // We need to add 2 because loadURIWithFlags may have
647 // cancelled a pending load which would have cleared
648 // its anchor scroll detection temporary increment.
649 if (aWebProgress.isTopLevel)
650 this.mBrowser.userTypedClear += 2;
652 if (this._shouldShowProgress(aRequest)) {
653 if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) {
654 this.mTab.setAttribute("busy", "true");
655 if (!(aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_RELOAD))
656 this.mTabBrowser.setTabTitleLoading(this.mTab);
659 if (this.mTab.selected)
660 this.mTabBrowser.mIsBusy = true;
663 else if (aStateFlags & nsIWebProgressListener.STATE_STOP &&
664 aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
666 if (this.mTab.hasAttribute("busy")) {
667 this.mTab.removeAttribute("busy");
668 this.mTabBrowser._tabAttrModified(this.mTab);
669 if (!this.mTab.selected)
670 this.mTab.setAttribute("unread", "true");
672 this.mTab.removeAttribute("progress");
674 if (aWebProgress.isTopLevel) {
675 if (!Components.isSuccessCode(aStatus) &&
676 !isTabEmpty(this.mTab)) {
677 // Restore the current document's location in case the
678 // request was stopped (possibly from a content script)
679 // before the location changed.
681 this.mBrowser.userTypedValue = null;
683 if (this.mTab.selected && gURLBar)
686 // The document is done loading, we no longer want the
689 if (this.mBrowser.userTypedClear > 1)
690 this.mBrowser.userTypedClear -= 2;
691 else if (this.mBrowser.userTypedClear > 0)
692 this.mBrowser.userTypedClear--;
695 if (!this.mBrowser.mIconURL)
696 this.mTabBrowser.useDefaultIcon(this.mTab);
702 var location = aRequest.QueryInterface(nsIChannel).URI;
704 // For keyword URIs clear the user typed value since they will be changed into real URIs
705 if (location.scheme == "keyword")
706 this.mBrowser.userTypedValue = null;
708 if (this.mTab.label == this.mTabBrowser.mStringBundle.getString("tabs.connecting"))
709 this.mTabBrowser.setTabTitle(this.mTab);
711 if (this.mTab.selected)
712 this.mTabBrowser.mIsBusy = false;
716 this._callProgressListeners("onUpdateCurrentBrowser",
717 [aStateFlags, aStatus, "", 0],
720 this._callProgressListeners("onStateChange",
721 [aWebProgress, aRequest, aStateFlags, aStatus],
725 this._callProgressListeners("onStateChange",
726 [aWebProgress, aRequest, aStateFlags, aStatus],
729 if (aStateFlags & (nsIWebProgressListener.STATE_START |
730 nsIWebProgressListener.STATE_STOP)) {
731 // reset cached temporary values at beginning and end
733 this.mTotalProgress = 0;
735 this.mStateFlags = aStateFlags;
736 this.mStatus = aStatus;
739 onLocationChange: function (aWebProgress, aRequest, aLocation,
741 // OnLocationChange is called for both the top-level content
742 // and the subframes.
743 let topLevel = aWebProgress.isTopLevel;
746 // If userTypedClear > 0, the document loaded correctly and we should be
747 // clearing the user typed value. We also need to clear the typed value
748 // if the document failed to load, to make sure the urlbar reflects the
749 // failed URI (particularly for SSL errors). However, don't clear the value
750 // if the error page's URI is about:blank, because that causes complete
751 // loss of urlbar contents for invalid URI errors (see bug 867957).
752 if (this.mBrowser.userTypedClear > 0 ||
753 ((aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) &&
754 aLocation.spec != "about:blank"))
755 this.mBrowser.userTypedValue = null;
757 // Clear out the missing plugins list since it's related to the
758 // previous location.
759 this.mBrowser.missingPlugins = null;
761 if (this.mTabBrowser.isFindBarInitialized(this.mTab)) {
762 let findBar = this.mTabBrowser.getFindBar(this.mTab);
764 // Close the Find toolbar if we're in old-style TAF mode
765 if (findBar.findMode != findBar.FIND_NORMAL)
768 // fix bug 253793 - turn off highlight when page changes
769 findBar.getElement("highlight").checked = false;
772 // Don't clear the favicon if this onLocationChange was
773 // triggered by a pushState or a replaceState. See bug 550565.
774 if (aWebProgress.isLoadingDocument &&
775 !(aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_PUSHSTATE)) {
776 this.mBrowser.mIconURL = null;
779 let autocomplete = this.mTabBrowser._placesAutocomplete;
780 let unifiedComplete = this.mTabBrowser._unifiedComplete;
781 if (this.mBrowser.registeredOpenURI) {
782 autocomplete.unregisterOpenPage(this.mBrowser.registeredOpenURI);
783 unifiedComplete.unregisterOpenPage(this.mBrowser.registeredOpenURI);
784 delete this.mBrowser.registeredOpenURI;
786 // Tabs in private windows aren't registered as "Open" so
787 // that they don't appear as switch-to-tab candidates.
788 if (!isBlankPageURL(aLocation.spec) &&
789 (!PrivateBrowsingUtils.isWindowPrivate(window) ||
790 PrivateBrowsingUtils.permanentPrivateBrowsing)) {
791 autocomplete.registerOpenPage(aLocation);
792 unifiedComplete.registerOpenPage(aLocation);
793 this.mBrowser.registeredOpenURI = aLocation;
798 this._callProgressListeners("onLocationChange",
799 [aWebProgress, aRequest, aLocation,
804 this.mBrowser.lastURI = aLocation;
805 this.mBrowser.lastLocationChange = Date.now();
809 onStatusChange: function (aWebProgress, aRequest, aStatus, aMessage) {
813 this._callProgressListeners("onStatusChange",
814 [aWebProgress, aRequest, aStatus, aMessage]);
816 this.mMessage = aMessage;
819 onSecurityChange: function (aWebProgress, aRequest, aState) {
820 this._callProgressListeners("onSecurityChange",
821 [aWebProgress, aRequest, aState]);
824 onRefreshAttempted: function (aWebProgress, aURI, aDelay, aSameURI) {
825 return this._callProgressListeners("onRefreshAttempted",
826 [aWebProgress, aURI, aDelay, aSameURI]);
829 QueryInterface: function (aIID) {
830 if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
831 aIID.equals(Components.interfaces.nsIWebProgressListener2) ||
832 aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
833 aIID.equals(Components.interfaces.nsISupports))
835 throw Components.results.NS_NOINTERFACE;
842 <method name="setIcon">
843 <parameter name="aTab"/>
844 <parameter name="aURI"/>
847 var browser = this.getBrowserForTab(aTab);
848 browser.mIconURL = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
850 if (aURI && this.mFaviconService) {
851 if (!(aURI instanceof Ci.nsIURI))
852 aURI = makeURI(aURI);
853 this.mFaviconService.setAndFetchFaviconForPage(browser.currentURI,
855 PrivateBrowsingUtils.isWindowPrivate(window) ?
856 this.mFaviconService.FAVICON_LOAD_PRIVATE :
857 this.mFaviconService.FAVICON_LOAD_NON_PRIVATE);
860 let sizedIconUrl = browser.mIconURL || "";
862 sizedIconUrl = this.PlacesUtils.getImageURLForResolution(window, sizedIconUrl);
864 if (sizedIconUrl != aTab.getAttribute("image")) {
866 aTab.setAttribute("image", sizedIconUrl);
868 aTab.removeAttribute("image");
869 this._tabAttrModified(aTab);
872 this._callProgressListeners(browser, "onLinkIconAvailable", [browser.mIconURL]);
877 <method name="getIcon">
878 <parameter name="aTab"/>
881 let browser = aTab ? this.getBrowserForTab(aTab) : this.selectedBrowser;
882 return browser.mIconURL;
887 <method name="shouldLoadFavIcon">
888 <parameter name="aURI"/>
892 Services.prefs.getBoolPref("browser.chrome.site_icons") &&
893 Services.prefs.getBoolPref("browser.chrome.favicons") &&
894 ("schemeIs" in aURI) && (aURI.schemeIs("http") || aURI.schemeIs("https")));
899 <method name="useDefaultIcon">
900 <parameter name="aTab"/>
903 var browser = this.getBrowserForTab(aTab);
904 var documentURI = browser.documentURI;
907 if (browser.imageDocument) {
908 if (Services.prefs.getBoolPref("browser.chrome.site_icons")) {
909 let sz = Services.prefs.getIntPref("browser.chrome.image_icons.max_size");
910 if (browser.imageDocument.width <= sz &&
911 browser.imageDocument.height <= sz) {
912 icon = browser.currentURI;
917 // Use documentURIObject in the check for shouldLoadFavIcon so that we
918 // do the right thing with about:-style error pages. Bug 453442
919 if (!icon && this.shouldLoadFavIcon(documentURI)) {
920 let url = documentURI.prePath + "/favicon.ico";
921 if (!this.isFailedIcon(url))
924 this.setIcon(aTab, icon);
929 <method name="isFailedIcon">
930 <parameter name="aURI"/>
933 if (this.mFaviconService) {
934 if (!(aURI instanceof Ci.nsIURI))
935 aURI = makeURI(aURI);
936 return this.mFaviconService.isFailedFavicon(aURI);
943 <method name="getWindowTitleForBrowser">
944 <parameter name="aBrowser"/>
948 var docElement = this.ownerDocument.documentElement;
949 var sep = docElement.getAttribute("titlemenuseparator");
951 // Strip out any null bytes in the content title, since the
952 // underlying widget implementations of nsWindow::SetTitle pass
953 // null-terminated strings to system APIs.
954 var docTitle = aBrowser.contentTitle.replace("\0", "", "g");
957 docTitle = docElement.getAttribute("titledefault");
959 var modifier = docElement.getAttribute("titlemodifier");
961 newTitle += docElement.getAttribute("titlepreface");
962 newTitle += docTitle;
966 newTitle += modifier;
968 // If location bar is hidden and the URL type supports a host,
969 // add the scheme and host to the title to prevent spoofing.
970 // XXX https://bugzilla.mozilla.org/show_bug.cgi?id=22183#c239
972 if (docElement.getAttribute("chromehidden").contains("location")) {
973 var uri = this.mURIFixup.createExposableURI(
974 aBrowser.currentURI);
975 if (uri.scheme == "about")
976 newTitle = uri.spec + sep + newTitle;
978 newTitle = uri.prePath + sep + newTitle;
987 <method name="updateTitlebar">
990 if ("TabView" in window && TabView.isVisible()) {
991 // ToDo: this will be removed when we gain ability to draw to the menu bar.
993 this.ownerDocument.title = TabView.windowTitle;
996 this.ownerDocument.title = this.getWindowTitleForBrowser(this.mCurrentBrowser);
1002 <method name="updateCurrentBrowser">
1003 <parameter name="aForceUpdate"/>
1006 var newBrowser = this.getBrowserAtIndex(this.tabContainer.selectedIndex);
1007 if (this.mCurrentBrowser == newBrowser && !aForceUpdate)
1010 if (!aForceUpdate) {
1011 TelemetryStopwatch.start("FX_TAB_SWITCH_UPDATE_MS");
1012 window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils)
1016 var oldTab = this.mCurrentTab;
1018 // Preview mode should not reset the owner
1019 if (!this._previewMode && !oldTab.selected)
1020 oldTab.owner = null;
1022 if (this._lastRelatedTab) {
1023 if (!this._lastRelatedTab.selected)
1024 this._lastRelatedTab.owner = null;
1025 this._lastRelatedTab = null;
1028 var oldBrowser = this.mCurrentBrowser;
1030 if (!gMultiProcessBrowser) {
1031 oldBrowser.setAttribute("type", "content-targetable");
1032 oldBrowser.docShellIsActive = false;
1033 newBrowser.setAttribute("type", "content-primary");
1034 newBrowser.docShellIsActive =
1035 (window.windowState != window.STATE_MINIMIZED);
1038 var updateBlockedPopups = false;
1039 if ((oldBrowser.blockedPopups && !newBrowser.blockedPopups) ||
1040 (!oldBrowser.blockedPopups && newBrowser.blockedPopups))
1041 updateBlockedPopups = true;
1043 this.mCurrentBrowser = newBrowser;
1044 this.mCurrentTab = this.tabContainer.selectedItem;
1045 this.showTab(this.mCurrentTab);
1047 var forwardButtonContainer = document.getElementById("urlbar-wrapper");
1048 if (forwardButtonContainer) {
1049 forwardButtonContainer.setAttribute("switchingtabs", "true");
1050 window.addEventListener("MozAfterPaint", function removeSwitchingtabsAttr() {
1051 window.removeEventListener("MozAfterPaint", removeSwitchingtabsAttr);
1052 forwardButtonContainer.removeAttribute("switchingtabs");
1056 this._appendStatusPanel();
1058 if (updateBlockedPopups)
1059 this.mCurrentBrowser.updateBlockedPopups();
1061 // Update the URL bar.
1062 var loc = this.mCurrentBrowser.currentURI;
1064 // Bug 666809 - SecurityUI support for e10s
1065 var webProgress = this.mCurrentBrowser.webProgress;
1066 var securityUI = this.mCurrentBrowser.securityUI;
1068 this._callProgressListeners(null, "onLocationChange",
1069 [webProgress, null, loc, 0], true,
1073 this._callProgressListeners(null, "onSecurityChange",
1074 [webProgress, null, securityUI.state], true, false);
1077 var listener = this.mTabListeners[this.tabContainer.selectedIndex] || null;
1078 if (listener && listener.mStateFlags) {
1079 this._callProgressListeners(null, "onUpdateCurrentBrowser",
1080 [listener.mStateFlags, listener.mStatus,
1081 listener.mMessage, listener.mTotalProgress],
1085 if (!this._previewMode) {
1086 this.mCurrentTab.removeAttribute("unread");
1087 oldTab.lastAccessed = Date.now();
1089 let oldFindBar = oldTab._findBar;
1091 oldFindBar.findMode == oldFindBar.FIND_NORMAL &&
1093 this._lastFindValue = oldFindBar._findField.value;
1095 this.updateTitlebar();
1097 this.mCurrentTab.removeAttribute("titlechanged");
1100 // If the new tab is busy, and our current state is not busy, then
1101 // we need to fire a start to all progress listeners.
1102 const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
1103 if (this.mCurrentTab.hasAttribute("busy") && !this.mIsBusy) {
1104 this.mIsBusy = true;
1105 this._callProgressListeners(null, "onStateChange",
1107 nsIWebProgressListener.STATE_START |
1108 nsIWebProgressListener.STATE_IS_NETWORK, 0],
1112 // If the new tab is not busy, and our current state is busy, then
1113 // we need to fire a stop to all progress listeners.
1114 if (!this.mCurrentTab.hasAttribute("busy") && this.mIsBusy) {
1115 this.mIsBusy = false;
1116 this._callProgressListeners(null, "onStateChange",
1118 nsIWebProgressListener.STATE_STOP |
1119 nsIWebProgressListener.STATE_IS_NETWORK, 0],
1123 this._setCloseKeyState(!this.mCurrentTab.pinned);
1125 // TabSelect events are suppressed during preview mode to avoid confusing extensions and other bits of code
1126 // that might rely upon the other changes suppressed.
1127 // Focus is suppressed in the event that the main browser window is minimized - focusing a tab would restore the window
1128 if (!this._previewMode) {
1129 // We've selected the new tab, so go ahead and notify listeners.
1130 let event = new CustomEvent("TabSelect", {
1137 this.mCurrentTab.dispatchEvent(event);
1139 this._tabAttrModified(oldTab);
1140 this._tabAttrModified(this.mCurrentTab);
1142 if (oldBrowser != newBrowser &&
1143 oldBrowser.docShell &&
1144 oldBrowser.docShell.contentViewer.inPermitUnload) {
1145 // Since the user is switching away from a tab that has
1146 // a beforeunload prompt active, we remove the prompt.
1147 // This prevents confusing user flows like the following:
1148 // 1. User attempts to close Firefox
1149 // 2. User switches tabs (ingoring a beforeunload prompt)
1150 // 3. User returns to tab, presses "Leave page"
1151 let promptBox = this.getTabModalPromptBox(oldBrowser);
1152 let prompts = promptBox.listPrompts();
1153 // There might not be any prompts here if the tab was closed
1154 // while in an onbeforeunload prompt, which will have
1155 // destroyed aforementioned prompt already, so check there's
1156 // something to remove, first:
1157 if (prompts.length) {
1158 // NB: This code assumes that the beforeunload prompt
1159 // is the top-most prompt on the tab.
1160 promptBox.removePrompt(prompts[prompts.length - 1]);
1164 if (!gMultiProcessBrowser)
1165 this._adjustFocusAfterTabSwitch(this.mCurrentTab, oldTab);
1168 this.tabContainer._setPositionalAttributes();
1171 TelemetryStopwatch.finish("FX_TAB_SWITCH_UPDATE_MS");
1176 <method name="_adjustFocusAfterTabSwitch">
1177 <parameter name="newTab"/>
1178 <parameter name="oldTab"/>
1180 let newBrowser = this.getBrowserForTab(newTab);
1181 let oldBrowser = this.getBrowserForTab(oldTab);
1184 oldBrowser._urlbarFocused = (gURLBar && gURLBar.focused);
1185 if (this.isFindBarInitialized(oldTab)) {
1186 let findBar = this.getFindBar(oldTab);
1187 oldTab._findBarFocused = (!findBar.hidden &&
1188 findBar._findField.getAttribute("focused") == "true");
1192 // When focus is in the tab bar, retain it there.
1193 if (document.activeElement == oldTab) {
1194 // We need to explicitly focus the new tab, because
1195 // tabbox.xml does this only in some cases.
1196 this.mCurrentTab.focus();
1200 // If there's a tabmodal prompt showing, focus it.
1201 if (newBrowser.hasAttribute("tabmodalPromptShowing")) {
1202 let XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
1203 let prompts = newBrowser.parentNode.getElementsByTagNameNS(XUL_NS, "tabmodalprompt");
1204 let prompt = prompts[prompts.length - 1];
1205 prompt.Dialog.setDefaultFocus();
1209 // Focus the location bar if it was previously focused for that tab.
1210 // In full screen mode, only bother making the location bar visible
1211 // if the tab is a blank one.
1212 if (newBrowser._urlbarFocused && gURLBar) {
1214 // Explicitly close the popup if the URL bar retains focus
1215 gURLBar.closePopup();
1217 if (!window.fullScreen) {
1222 if (isTabEmpty(this.mCurrentTab)) {
1223 focusAndSelectUrlBar();
1228 // Focus the find bar if it was previously focused for that tab.
1229 if (gFindBarInitialized && !gFindBar.hidden &&
1230 this.selectedTab._findBarFocused) {
1231 gFindBar._findField.focus();
1235 // Otherwise, focus the content area. If we're not using remote tabs, we
1236 // can focus the content area right away, since tab switching is synchronous.
1237 // If we're using remote tabs, we have to wait until after we've finalized
1238 // switching the tabs.
1240 if (newTab._skipContentFocus) {
1241 // It's possible the tab we're switching to is ready to focus asynchronously,
1242 // when we've already focused something else. In that case, this
1243 // _skipContentFocus property can be set so that we skip focusing the
1244 // content after we switch tabs.
1245 delete newTab._skipContentFocus;
1249 let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
1250 let focusFlags = fm.FLAG_NOSCROLL;
1252 if (!gMultiProcessBrowser) {
1253 let newFocusedElement = fm.getFocusedElementForWindow(window.content, true, {});
1255 // for anchors, use FLAG_SHOWRING so that it is clear what link was
1256 // last clicked when switching back to that tab
1257 if (newFocusedElement &&
1258 (newFocusedElement instanceof HTMLAnchorElement ||
1259 newFocusedElement.getAttributeNS("http://www.w3.org/1999/xlink", "type") == "simple"))
1260 focusFlags |= fm.FLAG_SHOWRING;
1263 fm.setFocus(newBrowser, focusFlags);
1267 <method name="_tabAttrModified">
1268 <parameter name="aTab"/>
1273 // This event should be dispatched when any of these attributes change:
1274 // label, crop, busy, image, selected
1275 var event = document.createEvent("Events");
1276 event.initEvent("TabAttrModified", true, false);
1277 aTab.dispatchEvent(event);
1281 <method name="setTabTitleLoading">
1282 <parameter name="aTab"/>
1285 aTab.label = this.mStringBundle.getString("tabs.connecting");
1287 this._tabAttrModified(aTab);
1292 <method name="setTabTitle">
1293 <parameter name="aTab"/>
1296 var browser = this.getBrowserForTab(aTab);
1298 var title = browser.contentTitle;
1301 if (browser.currentURI.spec) {
1303 title = this.mURIFixup.createExposableURI(browser.currentURI).spec;
1305 title = browser.currentURI.spec;
1309 if (title && !isBlankPageURL(title)) {
1310 // At this point, we now have a URI.
1311 // Let's try to unescape it using a character set
1312 // in case the URI is not ASCII.
1314 var characterSet = browser.characterSet;
1315 const textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"]
1316 .getService(Components.interfaces.nsITextToSubURI);
1317 title = textToSubURI.unEscapeNonAsciiURI(characterSet, title);
1318 } catch(ex) { /* Do nothing. */ }
1322 } else // Still no title? Fall back to our untitled string.
1323 title = this.mStringBundle.getString("tabs.emptyTabTitle");
1326 if (aTab.label == title &&
1332 this._tabAttrModified(aTab);
1335 this.updateTitlebar();
1342 <method name="loadOneTab">
1343 <parameter name="aURI"/>
1344 <parameter name="aReferrerURI"/>
1345 <parameter name="aCharset"/>
1346 <parameter name="aPostData"/>
1347 <parameter name="aLoadInBackground"/>
1348 <parameter name="aAllowThirdPartyFixup"/>
1352 var aRelatedToCurrent;
1353 var aAllowMixedContent;
1355 if (arguments.length == 2 &&
1356 typeof arguments[1] == "object" &&
1357 !(arguments[1] instanceof Ci.nsIURI)) {
1358 let params = arguments[1];
1359 aReferrerURI = params.referrerURI;
1360 aCharset = params.charset;
1361 aPostData = params.postData;
1362 aLoadInBackground = params.inBackground;
1363 aAllowThirdPartyFixup = params.allowThirdPartyFixup;
1364 aFromExternal = params.fromExternal;
1365 aRelatedToCurrent = params.relatedToCurrent;
1366 aAllowMixedContent = params.allowMixedContent;
1367 aSkipAnimation = params.skipAnimation;
1370 var bgLoad = (aLoadInBackground != null) ? aLoadInBackground :
1371 Services.prefs.getBoolPref("browser.tabs.loadInBackground");
1372 var owner = bgLoad ? null : this.selectedTab;
1373 var tab = this.addTab(aURI, {
1374 referrerURI: aReferrerURI,
1376 postData: aPostData,
1378 allowThirdPartyFixup: aAllowThirdPartyFixup,
1379 fromExternal: aFromExternal,
1380 relatedToCurrent: aRelatedToCurrent,
1381 skipAnimation: aSkipAnimation,
1382 allowMixedContent: aAllowMixedContent });
1384 this.selectedTab = tab;
1391 <method name="loadTabs">
1392 <parameter name="aURIs"/>
1393 <parameter name="aLoadInBackground"/>
1394 <parameter name="aReplace"/>
1399 // The tab selected after this new tab is closed (i.e. the new tab's
1400 // "owner") is the next adjacent tab (i.e. not the previously viewed tab)
1401 // when several urls are opened here (i.e. closing the first should select
1402 // the next of many URLs opened) or if the pref to have UI links opened in
1403 // the background is set (i.e. the link is not being opened modally)
1406 // Number of URLs Load UI Links in BG Focus Last Viewed?
1409 // > 1 false/true NO
1410 var multiple = aURIs.length > 1;
1411 var owner = multiple || aLoadInBackground ? null : this.selectedTab;
1412 var firstTabAdded = null;
1416 this.loadURI(aURIs[0], null, null);
1418 // Ignore failure in case a URI is wrong, so we can continue
1419 // opening the next ones.
1423 firstTabAdded = this.addTab(aURIs[0], {ownerTab: owner, skipAnimation: multiple});
1425 var tabNum = this.tabContainer.selectedIndex;
1426 for (let i = 1; i < aURIs.length; ++i) {
1427 let tab = this.addTab(aURIs[i], {skipAnimation: true});
1429 this.moveTabTo(tab, ++tabNum);
1432 if (!aLoadInBackground) {
1433 if (firstTabAdded) {
1434 // .selectedTab setter focuses the content area
1435 this.selectedTab = firstTabAdded;
1438 this.selectedBrowser.focus();
1443 <method name="updateBrowserRemoteness">
1444 <parameter name="aBrowser"/>
1445 <parameter name="aShouldBeRemote"/>
1448 let isRemote = aBrowser.getAttribute("remote") == "true";
1449 if (isRemote == aShouldBeRemote)
1452 let wasActive = document.activeElement == aBrowser;
1454 // Unhook our progress listener.
1455 let tab = this._getTabForBrowser(aBrowser);
1456 let index = tab._tPos;
1457 let filter = this.mTabFilters[index];
1458 aBrowser.webProgress.removeProgressListener(filter);
1460 // Change the "remote" attribute.
1461 let parent = aBrowser.parentNode;
1462 parent.removeChild(aBrowser);
1463 aBrowser.setAttribute("remote", aShouldBeRemote ? "true" : "false");
1464 parent.appendChild(aBrowser);
1466 // Restore the progress listener.
1467 aBrowser.webProgress.addProgressListener(filter, Ci.nsIWebProgress.NOTIFY_ALL);
1469 if (aShouldBeRemote)
1470 tab.setAttribute("remote", "true");
1472 tab.removeAttribute("remote");
1482 #ifdef MAKE_E10S_WORK
1483 <method name="updateBrowserRemotenessByURL">
1484 <parameter name="aBrowser"/>
1485 <parameter name="aURL"/>
1488 let shouldBeRemote = this._shouldBrowserBeRemote(aURL);
1489 return this.updateBrowserRemoteness(aBrowser, shouldBeRemote);
1495 Returns true if we want to load the content for this URL in a
1496 remote process. Eventually this should just check whether aURL
1497 is unprivileged. Right now, though, we would like to load
1498 some unprivileged URLs (like about:neterror) in the main
1499 process since they interact with chrome code through
1502 <method name="_shouldBrowserBeRemote">
1503 <parameter name="aURL"/>
1506 if (!gMultiProcessBrowser)
1509 // loadURI in browser.xml treats null as about:blank
1511 aURL = "about:blank";
1513 if (aURL.startsWith("about:") &&
1514 aURL.toLowerCase() != "about:home" &&
1515 aURL.toLowerCase() != "about:blank") {
1519 if (aURL.startsWith("chrome:"))
1528 <method name="addTab">
1529 <parameter name="aURI"/>
1530 <parameter name="aReferrerURI"/>
1531 <parameter name="aCharset"/>
1532 <parameter name="aPostData"/>
1533 <parameter name="aOwner"/>
1534 <parameter name="aAllowThirdPartyFixup"/>
1537 const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
1539 var aRelatedToCurrent;
1541 var aAllowMixedContent;
1542 if (arguments.length == 2 &&
1543 typeof arguments[1] == "object" &&
1544 !(arguments[1] instanceof Ci.nsIURI)) {
1545 let params = arguments[1];
1546 aReferrerURI = params.referrerURI;
1547 aCharset = params.charset;
1548 aPostData = params.postData;
1549 aOwner = params.ownerTab;
1550 aAllowThirdPartyFixup = params.allowThirdPartyFixup;
1551 aFromExternal = params.fromExternal;
1552 aRelatedToCurrent = params.relatedToCurrent;
1553 aSkipAnimation = params.skipAnimation;
1554 aAllowMixedContent = params.allowMixedContent;
1557 // if we're adding tabs, we're past interrupt mode, ditch the owner
1558 if (this.mCurrentTab.owner)
1559 this.mCurrentTab.owner = null;
1561 var t = document.createElementNS(NS_XUL, "tab");
1563 var uriIsAboutBlank = !aURI || aURI == "about:blank";
1565 t.setAttribute("crop", "end");
1566 t.setAttribute("onerror", "this.removeAttribute('image');");
1567 t.className = "tabbrowser-tab";
1569 #ifdef MAKE_E10S_WORK
1570 let remote = this._shouldBrowserBeRemote(aURI);
1572 let remote = gMultiProcessBrowser;
1575 t.setAttribute("remote", "true");
1577 this.tabContainer._unlockTabSizing();
1579 // When overflowing, new tabs are scrolled into view smoothly, which
1580 // doesn't go well together with the width transition. So we skip the
1581 // transition in that case.
1582 let animate = !aSkipAnimation &&
1583 this.tabContainer.getAttribute("overflow") != "true" &&
1584 Services.prefs.getBoolPref("browser.tabs.animate");
1586 t.setAttribute("fadein", "true");
1587 setTimeout(function (tabContainer) {
1588 tabContainer._handleNewTab(t);
1589 }, 0, this.tabContainer);
1592 // invalidate caches
1593 this._browsers = null;
1594 this._visibleTabs = null;
1596 this.tabContainer.appendChild(t);
1598 // If this new tab is owned by another, assert that relationship
1602 var b = document.createElementNS(
1603 "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
1605 b.setAttribute("type", "content-targetable");
1606 b.setAttribute("message", "true");
1607 b.setAttribute("messagemanagergroup", "browsers");
1608 b.setAttribute("contextmenu", this.getAttribute("contentcontextmenu"));
1609 b.setAttribute("tooltip", this.getAttribute("contenttooltip"));
1612 b.setAttribute("remote", "true");
1614 if (window.gShowPageResizers && window.windowState == window.STATE_NORMAL) {
1615 b.setAttribute("showresizer", "true");
1618 if (this.hasAttribute("autocompletepopup"))
1619 b.setAttribute("autocompletepopup", this.getAttribute("autocompletepopup"));
1621 if (this.hasAttribute("selectpopup"))
1622 b.setAttribute("selectpopup", this.getAttribute("selectpopup"));
1624 b.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
1626 // Create the browserStack container
1627 var stack = document.createElementNS(NS_XUL, "stack");
1628 stack.className = "browserStack";
1629 stack.appendChild(b);
1630 stack.setAttribute("flex", "1");
1632 // Create the browserContainer
1633 var browserContainer = document.createElementNS(NS_XUL, "vbox");
1634 browserContainer.className = "browserContainer";
1635 browserContainer.appendChild(stack);
1636 browserContainer.setAttribute("flex", "1");
1638 // Create the sidebar container
1639 var browserSidebarContainer = document.createElementNS(NS_XUL,
1641 browserSidebarContainer.className = "browserSidebarContainer";
1642 browserSidebarContainer.appendChild(browserContainer);
1643 browserSidebarContainer.setAttribute("flex", "1");
1645 // Add the Message and the Browser to the box
1646 var notificationbox = document.createElementNS(NS_XUL,
1648 notificationbox.setAttribute("flex", "1");
1649 notificationbox.appendChild(browserSidebarContainer);
1651 var position = this.tabs.length - 1;
1652 var uniqueId = this._generateUniquePanelID();
1653 notificationbox.id = uniqueId;
1654 t.linkedPanel = uniqueId;
1655 t.linkedBrowser = b;
1657 this.tabContainer._setPositionalAttributes();
1659 // Prevent the superfluous initial load of a blank document
1660 // if we're going to load something other than about:blank.
1661 if (!uriIsAboutBlank) {
1662 b.setAttribute("nodefaultsrc", "true");
1665 // NB: this appendChild call causes us to run constructors for the
1666 // browser element, which fires off a bunch of notifications. Some
1667 // of those notifications can cause code to run that inspects our
1668 // state, so it is important that the tab element is fully
1669 // initialized by this point.
1670 this.mPanelContainer.appendChild(notificationbox);
1672 // We've waited until the tab is in the DOM to set the label. This
1673 // allows the TabLabelModified event to be properly dispatched.
1674 if (!aURI || isBlankPageURL(aURI)) {
1675 t.label = this.mStringBundle.getString("tabs.emptyTabTitle");
1678 this.tabContainer.updateVisibility();
1680 // wire up a progress listener for the new browser object.
1681 var tabListener = this.mTabProgressListener(t, b, uriIsAboutBlank);
1682 const filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
1683 .createInstance(Components.interfaces.nsIWebProgress);
1684 filter.addProgressListener(tabListener, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
1685 b.webProgress.addProgressListener(filter, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
1686 this.mTabListeners[position] = tabListener;
1687 this.mTabFilters[position] = filter;
1689 b.droppedLinkHandler = handleDroppedLink;
1691 // If we just created a new tab that loads the default
1692 // newtab url, swap in a preloaded page if possible.
1693 // Do nothing if we're a private window.
1694 let docShellsSwapped = false;
1695 if (aURI == BROWSER_NEW_TAB_URL &&
1696 !PrivateBrowsingUtils.isWindowPrivate(window) &&
1697 !gMultiProcessBrowser) {
1698 docShellsSwapped = gBrowserNewTabPreloader.newTab(t);
1699 } else if (aURI == "about:customizing") {
1700 docShellsSwapped = gCustomizationTabPreloader.newTab(t);
1703 // Dispatch a new tab notification. We do this once we're
1704 // entirely done, so that things are in a consistent state
1705 // even if the event listener opens or closes tabs.
1706 var evt = document.createEvent("Events");
1707 evt.initEvent("TabOpen", true, false);
1708 t.dispatchEvent(evt);
1710 // If we didn't swap docShells with a preloaded browser
1711 // then let's just continue loading the page normally.
1712 if (!docShellsSwapped && !uriIsAboutBlank) {
1713 // pretend the user typed this so it'll be available till
1714 // the document successfully loads
1715 if (aURI && gInitialPages.indexOf(aURI) == -1)
1716 b.userTypedValue = aURI;
1718 let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
1719 if (aAllowThirdPartyFixup) {
1720 flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
1721 flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
1724 flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL;
1725 if (aAllowMixedContent)
1726 flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_MIXED_CONTENT;
1728 b.loadURIWithFlags(aURI, flags, aReferrerURI, aCharset, aPostData);
1734 // We start our browsers out as inactive, and then maintain
1735 // activeness in the tab switcher.
1736 b.docShellIsActive = false;
1738 // Check if we're opening a tab related to the current tab and
1739 // move it to after the current tab.
1740 // aReferrerURI is null or undefined if the tab is opened from
1741 // an external application or bookmark, i.e. somewhere other
1742 // than the current tab.
1743 if ((aRelatedToCurrent == null ? aReferrerURI : aRelatedToCurrent) &&
1744 Services.prefs.getBoolPref("browser.tabs.insertRelatedAfterCurrent")) {
1745 let newTabPos = (this._lastRelatedTab ||
1746 this.selectedTab)._tPos + 1;
1747 if (this._lastRelatedTab)
1748 this._lastRelatedTab.owner = null;
1750 t.owner = this.selectedTab;
1751 this.moveTabTo(t, newTabPos);
1752 this._lastRelatedTab = t;
1756 mozRequestAnimationFrame(function () {
1757 this.tabContainer._handleTabTelemetryStart(t, aURI);
1759 // kick the animation off
1760 t.setAttribute("fadein", "true");
1769 <method name="warnAboutClosingTabs">
1770 <parameter name="aCloseTabs"/>
1771 <parameter name="aTab"/>
1775 switch (aCloseTabs) {
1776 case this.closingTabsEnum.ALL:
1777 tabsToClose = this.tabs.length - this._removingTabs.length -
1778 gBrowser._numPinnedTabs;
1780 case this.closingTabsEnum.OTHER:
1781 tabsToClose = this.visibleTabs.length - 1 - gBrowser._numPinnedTabs;
1783 case this.closingTabsEnum.TO_END:
1785 throw new Error("Required argument missing: aTab");
1787 tabsToClose = this.getTabsToTheEndFrom(aTab).length;
1790 throw new Error("Invalid argument: " + aCloseTabs);
1793 if (tabsToClose <= 1)
1796 const pref = aCloseTabs == this.closingTabsEnum.ALL ?
1797 "browser.tabs.warnOnClose" : "browser.tabs.warnOnCloseOtherTabs";
1798 var shouldPrompt = Services.prefs.getBoolPref(pref);
1802 var ps = Services.prompt;
1804 // default to true: if it were false, we wouldn't get this far
1805 var warnOnClose = { value: true };
1806 var bundle = this.mStringBundle;
1808 // focus the window before prompting.
1809 // this will raise any minimized window, which will
1810 // make it obvious which window the prompt is for and will
1811 // solve the problem of windows "obscuring" the prompt.
1812 // see bug #350299 for more details
1814 var warningMessage =
1815 PluralForm.get(tabsToClose, bundle.getString("tabs.closeWarningMultiple"))
1816 .replace("#1", tabsToClose);
1818 ps.confirmEx(window,
1819 bundle.getString("tabs.closeWarningTitle"),
1821 (ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_0)
1822 + (ps.BUTTON_TITLE_CANCEL * ps.BUTTON_POS_1),
1823 bundle.getString("tabs.closeButtonMultiple"),
1825 aCloseTabs == this.closingTabsEnum.ALL ?
1826 bundle.getString("tabs.closeWarningPromptMe") : null,
1828 var reallyClose = (buttonPressed == 0);
1830 // don't set the pref unless they press OK and it's false
1831 if (aCloseTabs == this.closingTabsEnum.ALL && reallyClose && !warnOnClose.value)
1832 Services.prefs.setBoolPref(pref, false);
1839 <method name="getTabsToTheEndFrom">
1840 <parameter name="aTab"/>
1844 let tabs = this.visibleTabs;
1845 for (let i = tabs.length - 1; tabs[i] != aTab && i >= 0; --i) {
1846 tabsToEnd.push(tabs[i]);
1848 return tabsToEnd.reverse();
1853 <method name="removeTabsToTheEndFrom">
1854 <parameter name="aTab"/>
1857 if (this.warnAboutClosingTabs(this.closingTabsEnum.TO_END, aTab)) {
1858 let tabs = this.getTabsToTheEndFrom(aTab);
1859 for (let i = tabs.length - 1; i >= 0; --i) {
1860 this.removeTab(tabs[i], {animate: true});
1867 <method name="removeAllTabsBut">
1868 <parameter name="aTab"/>
1874 if (this.warnAboutClosingTabs(this.closingTabsEnum.OTHER)) {
1875 let tabs = this.visibleTabs;
1876 this.selectedTab = aTab;
1878 for (let i = tabs.length - 1; i >= 0; --i) {
1879 if (tabs[i] != aTab && !tabs[i].pinned)
1880 this.removeTab(tabs[i], {animate: true});
1887 <method name="removeCurrentTab">
1888 <parameter name="aParams"/>
1891 this.removeTab(this.mCurrentTab, aParams);
1896 <field name="_removingTabs">
1900 <method name="removeTab">
1901 <parameter name="aTab"/>
1902 <parameter name="aParams"/>
1906 var animate = aParams.animate;
1907 var byMouse = aParams.byMouse;
1910 // Handle requests for synchronously removing an already
1911 // asynchronously closing tab.
1914 this._endRemoveTab(aTab);
1918 var isLastTab = (this.tabs.length - this._removingTabs.length == 1);
1920 if (!this._beginRemoveTab(aTab, false, null, true))
1923 if (!aTab.pinned && !aTab.hidden && aTab._fullyOpen && byMouse)
1924 this.tabContainer._lockTabSizing(aTab);
1926 this.tabContainer._unlockTabSizing();
1928 if (!animate /* the caller didn't opt in */ ||
1932 this._removingTabs.length > 3 /* don't want lots of concurrent animations */ ||
1933 aTab.getAttribute("fadein") != "true" /* fade-in transition hasn't been triggered yet */ ||
1934 window.getComputedStyle(aTab).maxWidth == "0.1px" /* fade-in transition hasn't moved yet */ ||
1935 !Services.prefs.getBoolPref("browser.tabs.animate")) {
1936 this._endRemoveTab(aTab);
1940 this.tabContainer._handleTabTelemetryStart(aTab);
1942 this._blurTab(aTab);
1943 aTab.style.maxWidth = ""; // ensure that fade-out transition happens
1944 aTab.removeAttribute("fadein");
1946 setTimeout(function (tab, tabbrowser) {
1947 if (tab.parentNode &&
1948 window.getComputedStyle(tab).maxWidth == "0.1px") {
1949 NS_ASSERT(false, "Giving up waiting for the tab closing animation to finish (bug 608589)");
1950 tabbrowser._endRemoveTab(tab);
1952 }, 3000, aTab, this);
1957 <!-- Tab close requests are ignored if the window is closing anyway,
1958 e.g. when holding Ctrl+W. -->
1959 <field name="_windowIsClosing">
1963 <method name="_beginRemoveTab">
1964 <parameter name="aTab"/>
1965 <parameter name="aTabWillBeMoved"/>
1966 <parameter name="aCloseWindowWithLastTab"/>
1967 <parameter name="aCloseWindowFastpath"/>
1971 this._windowIsClosing)
1974 var browser = this.getBrowserForTab(aTab);
1975 if (!aTab._pendingPermitUnload && !aTabWillBeMoved) {
1976 let ds = browser.docShell;
1977 if (ds && ds.contentViewer) {
1978 // We need to block while calling permitUnload() because it
1979 // processes the event queue and may lead to another removeTab()
1980 // call before permitUnload() even returned.
1981 aTab._pendingPermitUnload = true;
1982 let permitUnload = ds.contentViewer.permitUnload();
1983 delete aTab._pendingPermitUnload;
1984 // If we were closed during onbeforeunload, we return false now
1985 // so we don't (try to) close the same tab again. Of course, we
1986 // also stop if the unload was cancelled by the user:
1987 if (aTab.closing || !permitUnload) {
1988 // NB: deliberately keep the _closedDuringPermitUnload set to
1989 // true so we keep exiting early in case of multiple calls.
1995 var closeWindow = false;
1997 if (this.tabs.length - this._removingTabs.length == 1) {
1998 closeWindow = aCloseWindowWithLastTab != null ? aCloseWindowWithLastTab :
1999 !window.toolbar.visible ||
2000 Services.prefs.getBoolPref("browser.tabs.closeWindowWithLastTab");
2002 // Closing the tab and replacing it with a blank one is notably slower
2003 // than closing the window right away. If the caller opts in, take
2006 aCloseWindowFastpath &&
2007 this._removingTabs.length == 0) {
2008 // This call actually closes the window, unless the user
2009 // cancels the operation. We are finished here in both cases.
2010 this._windowIsClosing = window.closeWindow(true, window.warnAboutClosingWindow);
2017 aTab.closing = true;
2018 this._removingTabs.push(aTab);
2019 this._visibleTabs = null; // invalidate cache
2021 // Invalidate hovered tab state tracking for this closing tab.
2022 if (this.tabContainer._hoveredTab == aTab)
2026 this.addTab(BROWSER_NEW_TAB_URL, {skipAnimation: true});
2028 this.tabContainer.updateVisibility();
2030 // We're committed to closing the tab now.
2031 // Dispatch a notification.
2032 // We dispatch it before any teardown so that event listeners can
2033 // inspect the tab that's about to close.
2034 var evt = document.createEvent("UIEvent");
2035 evt.initUIEvent("TabClose", true, false, window, aTabWillBeMoved ? 1 : 0);
2036 aTab.dispatchEvent(evt);
2038 if (!aTabWillBeMoved && !gMultiProcessBrowser) {
2039 // Prevent this tab from showing further dialogs, since we're closing it
2040 var windowUtils = browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).
2041 getInterface(Ci.nsIDOMWindowUtils);
2042 windowUtils.disableDialogs();
2045 // Remove the tab's filter and progress listener.
2046 const filter = this.mTabFilters[aTab._tPos];
2048 browser.webProgress.removeProgressListener(filter);
2050 filter.removeProgressListener(this.mTabListeners[aTab._tPos]);
2051 this.mTabListeners[aTab._tPos].destroy();
2053 if (browser.registeredOpenURI && !aTabWillBeMoved) {
2054 this._placesAutocomplete.unregisterOpenPage(browser.registeredOpenURI);
2055 this._unifiedComplete.unregisterOpenPage(browser.registeredOpenURI);
2056 delete browser.registeredOpenURI;
2059 // We are no longer the primary content area.
2060 browser.setAttribute("type", "content-targetable");
2062 // Remove this tab as the owner of any other tabs, since it's going away.
2063 Array.forEach(this.tabs, function (tab) {
2064 if ("owner" in tab && tab.owner == aTab)
2065 // |tab| is a child of the tab we're removing, make it an orphan
2069 aTab._endRemoveArgs = [closeWindow, newTab];
2075 <method name="_endRemoveTab">
2076 <parameter name="aTab"/>
2079 if (!aTab || !aTab._endRemoveArgs)
2082 var [aCloseWindow, aNewTab] = aTab._endRemoveArgs;
2083 aTab._endRemoveArgs = null;
2085 if (this._windowIsClosing) {
2086 aCloseWindow = false;
2090 this._lastRelatedTab = null;
2092 // update the UI early for responsiveness
2093 aTab.collapsed = true;
2094 this.tabContainer._fillTrailingGap();
2095 this._blurTab(aTab);
2097 this._removingTabs.splice(this._removingTabs.indexOf(aTab), 1);
2100 this._windowIsClosing = true;
2101 while (this._removingTabs.length)
2102 this._endRemoveTab(this._removingTabs[0]);
2103 } else if (!this._windowIsClosing) {
2105 focusAndSelectUrlBar();
2107 // workaround for bug 345399
2108 this.tabContainer.mTabstrip._updateScrollButtonsDisabledState();
2111 // We're going to remove the tab and the browser now.
2112 // Clean up mTabFilters and mTabListeners now rather than in
2113 // _beginRemoveTab, so that their size is always in sync with the
2114 // number of tabs and browsers (the xbl destructor depends on this).
2115 this.mTabFilters.splice(aTab._tPos, 1);
2116 this.mTabListeners.splice(aTab._tPos, 1);
2118 var browser = this.getBrowserForTab(aTab);
2120 // Because of the way XBL works (fields just set JS
2121 // properties on the element) and the code we have in place
2122 // to preserve the JS objects for any elements that have
2123 // JS properties set on them, the browser element won't be
2124 // destroyed until the document goes away. So we force a
2125 // cleanup ourselves.
2126 // This has to happen before we remove the child so that the
2127 // XBL implementation of nsIObserver still works.
2130 var wasPinned = aTab.pinned;
2132 // Invalidate browsers cache, as the tab is removed from the
2134 this._browsers = null;
2136 // Remove the tab ...
2137 this.tabContainer.removeChild(aTab);
2139 // ... and fix up the _tPos properties immediately.
2140 for (let i = aTab._tPos; i < this.tabs.length; i++)
2141 this.tabs[i]._tPos = i;
2143 if (!this._windowIsClosing) {
2145 this.tabContainer._positionPinnedTabs();
2147 // update tab close buttons state
2148 this.tabContainer.adjustTabstrip();
2150 setTimeout(function(tabs) {
2151 tabs._lastTabClosedByMouse = false;
2152 }, 0, this.tabContainer);
2155 // update tab positional properties and attributes
2156 this.selectedTab._selected = true;
2157 this.tabContainer._setPositionalAttributes();
2159 // Removing the panel requires fixing up selectedPanel immediately
2160 // (see below), which would be hindered by the potentially expensive
2161 // browser removal. So we remove the browser and the panel in two
2164 var panel = this.getNotificationBox(browser);
2166 // This will unload the document. An unload handler could remove
2167 // dependant tabs, so it's important that the tabbrowser is now in
2168 // a consistent state (tab removed, tab positions updated, etc.).
2169 browser.parentNode.removeChild(browser);
2171 // Release the browser in case something is erroneously holding a
2172 // reference to the tab after its removal.
2173 aTab.linkedBrowser = null;
2175 // As the browser is removed, the removal of a dependent document can
2176 // cause the whole window to close. So at this point, it's possible
2177 // that the binding is destructed.
2179 this.mPanelContainer.removeChild(panel);
2183 this._windowIsClosing = closeWindow(true, window.warnAboutClosingWindow);
2188 <method name="_blurTab">
2189 <parameter name="aTab"/>
2196 !aTab.owner.hidden &&
2197 !aTab.owner.closing &&
2198 Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose")) {
2199 this.selectedTab = aTab.owner;
2203 // Switch to a visible tab unless there aren't any others remaining
2204 let remainingTabs = this.visibleTabs;
2205 let numTabs = remainingTabs.length;
2206 if (numTabs == 0 || numTabs == 1 && remainingTabs[0] == aTab) {
2207 remainingTabs = Array.filter(this.tabs, function(tab) {
2208 return !tab.closing;
2212 // Try to find a remaining tab that comes after the given tab
2215 tab = tab.nextSibling;
2216 } while (tab && remainingTabs.indexOf(tab) == -1);
2222 tab = tab.previousSibling;
2223 } while (tab && remainingTabs.indexOf(tab) == -1);
2226 this.selectedTab = tab;
2231 <method name="swapNewTabWithBrowser">
2232 <parameter name="aNewTab"/>
2233 <parameter name="aBrowser"/>
2236 // The browser must be standalone.
2237 if (aBrowser.getTabBrowser())
2238 throw Cr.NS_ERROR_INVALID_ARG;
2240 // The tab is definitely not loading.
2241 aNewTab.removeAttribute("busy");
2242 if (aNewTab.selected) {
2243 this.mIsBusy = false;
2246 this._swapBrowserDocShells(aNewTab, aBrowser);
2248 // Update the new tab's title.
2249 this.setTabTitle(aNewTab);
2251 if (aNewTab.selected) {
2252 this.updateCurrentBrowser(true);
2258 <method name="swapBrowsersAndCloseOther">
2259 <parameter name="aOurTab"/>
2260 <parameter name="aOtherTab"/>
2263 // Do not allow transfering a private tab to a non-private window
2265 if (PrivateBrowsingUtils.isWindowPrivate(window) !=
2266 PrivateBrowsingUtils.isWindowPrivate(aOtherTab.ownerDocument.defaultView))
2269 // That's gBrowser for the other window, not the tab's browser!
2270 var remoteBrowser = aOtherTab.ownerDocument.defaultView.gBrowser;
2271 var isPending = aOtherTab.hasAttribute("pending");
2273 // First, start teardown of the other browser. Make sure to not
2274 // fire the beforeunload event in the process. Close the other
2275 // window if this was its last tab.
2276 if (!remoteBrowser._beginRemoveTab(aOtherTab, true, true))
2279 let ourBrowser = this.getBrowserForTab(aOurTab);
2280 let otherBrowser = aOtherTab.linkedBrowser;
2282 // If the other tab is pending (i.e. has not been restored, yet)
2283 // then do not switch docShells but retrieve the other tab's state
2284 // and apply it to our tab.
2286 SessionStore.setTabState(aOurTab, SessionStore.getTabState(aOtherTab));
2288 // Make sure to unregister any open URIs.
2289 this._swapRegisteredOpenURIs(ourBrowser, otherBrowser);
2291 // Workarounds for bug 458697
2292 // Icon might have been set on DOMLinkAdded, don't override that.
2293 if (!ourBrowser.mIconURL && otherBrowser.mIconURL)
2294 this.setIcon(aOurTab, otherBrowser.mIconURL);
2295 var isBusy = aOtherTab.hasAttribute("busy");
2297 aOurTab.setAttribute("busy", "true");
2298 this._tabAttrModified(aOurTab);
2299 if (aOurTab.selected)
2300 this.mIsBusy = true;
2303 this._swapBrowserDocShells(aOurTab, otherBrowser);
2306 // Handle findbar data (if any)
2307 let otherFindBar = aOtherTab._findBar;
2309 otherFindBar.findMode == otherFindBar.FIND_NORMAL) {
2310 let ourFindBar = this.getFindBar(aOurTab);
2311 ourFindBar._findField.value = otherFindBar._findField.value;
2312 if (!otherFindBar.hidden)
2313 ourFindBar.onFindCommand();
2316 // Finish tearing down the tab that's going away.
2317 remoteBrowser._endRemoveTab(aOtherTab);
2320 this.setTabTitleLoading(aOurTab);
2322 this.setTabTitle(aOurTab);
2324 // If the tab was already selected (this happpens in the scenario
2325 // of replaceTabWithWindow), notify onLocationChange, etc.
2326 if (aOurTab.selected)
2327 this.updateCurrentBrowser(true);
2332 <method name="_swapBrowserDocShells">
2333 <parameter name="aOurTab"/>
2334 <parameter name="aOtherBrowser"/>
2337 // Unhook our progress listener
2338 let index = aOurTab._tPos;
2339 const filter = this.mTabFilters[index];
2340 let tabListener = this.mTabListeners[index];
2341 let ourBrowser = this.getBrowserForTab(aOurTab);
2342 ourBrowser.webProgress.removeProgressListener(filter);
2343 filter.removeProgressListener(tabListener);
2345 // Make sure to unregister any open URIs.
2346 this._swapRegisteredOpenURIs(ourBrowser, aOtherBrowser);
2348 // Give others a chance to swap state.
2349 let event = new CustomEvent("SwapDocShells", {"detail": aOtherBrowser});
2350 ourBrowser.dispatchEvent(event);
2351 event = new CustomEvent("SwapDocShells", {"detail": ourBrowser});
2352 aOtherBrowser.dispatchEvent(event);
2354 // Swap the docshells
2355 ourBrowser.swapDocShells(aOtherBrowser);
2357 // Restore the progress listener
2358 this.mTabListeners[index] = tabListener =
2359 this.mTabProgressListener(aOurTab, ourBrowser, false);
2361 const notifyAll = Ci.nsIWebProgress.NOTIFY_ALL;
2362 filter.addProgressListener(tabListener, notifyAll);
2363 ourBrowser.webProgress.addProgressListener(filter, notifyAll);
2368 <method name="_swapRegisteredOpenURIs">
2369 <parameter name="aOurBrowser"/>
2370 <parameter name="aOtherBrowser"/>
2373 // If the current URI is registered as open remove it from the list.
2374 if (aOurBrowser.registeredOpenURI) {
2375 this._placesAutocomplete.unregisterOpenPage(aOurBrowser.registeredOpenURI);
2376 this._unifiedComplete.unregisterOpenPage(aOurBrowser.registeredOpenURI);
2377 delete aOurBrowser.registeredOpenURI;
2380 // If the other/new URI is registered as open then copy it over.
2381 if (aOtherBrowser.registeredOpenURI) {
2382 aOurBrowser.registeredOpenURI = aOtherBrowser.registeredOpenURI;
2383 delete aOtherBrowser.registeredOpenURI;
2389 <method name="reloadAllTabs">
2392 let tabs = this.visibleTabs;
2393 let l = tabs.length;
2394 for (var i = 0; i < l; i++) {
2396 this.getBrowserForTab(tabs[i]).reload();
2398 // ignore failure to reload so others will be reloaded
2405 <method name="reloadTab">
2406 <parameter name="aTab"/>
2409 this.getBrowserForTab(aTab).reload();
2414 <method name="addProgressListener">
2415 <parameter name="aListener"/>
2418 if (arguments.length != 1) {
2419 Components.utils.reportError("gBrowser.addProgressListener was " +
2420 "called with a second argument, " +
2421 "which is not supported. See bug " +
2422 "608628. Call stack: " + new Error().stack);
2425 this.mProgressListeners.push(aListener);
2430 <method name="removeProgressListener">
2431 <parameter name="aListener"/>
2434 this.mProgressListeners =
2435 this.mProgressListeners.filter(function (l) l != aListener);
2440 <method name="addTabsProgressListener">
2441 <parameter name="aListener"/>
2443 this.mTabsProgressListeners.push(aListener);
2447 <method name="removeTabsProgressListener">
2448 <parameter name="aListener"/>
2451 this.mTabsProgressListeners =
2452 this.mTabsProgressListeners.filter(function (l) l != aListener);
2457 <method name="getBrowserForTab">
2458 <parameter name="aTab"/>
2461 return aTab.linkedBrowser;
2466 <method name="showOnlyTheseTabs">
2467 <parameter name="aTabs"/>
2470 Array.forEach(this.tabs, function(tab) {
2471 if (aTabs.indexOf(tab) == -1)
2477 this.tabContainer._handleTabSelect(false);
2482 <method name="showTab">
2483 <parameter name="aTab"/>
2487 aTab.removeAttribute("hidden");
2488 this._visibleTabs = null; // invalidate cache
2490 this.tabContainer.adjustTabstrip();
2492 this.tabContainer._setPositionalAttributes();
2494 let event = document.createEvent("Events");
2495 event.initEvent("TabShow", true, false);
2496 aTab.dispatchEvent(event);
2502 <method name="hideTab">
2503 <parameter name="aTab"/>
2506 if (!aTab.hidden && !aTab.pinned && !aTab.selected &&
2508 aTab.setAttribute("hidden", "true");
2509 this._visibleTabs = null; // invalidate cache
2511 this.tabContainer.adjustTabstrip();
2513 this.tabContainer._setPositionalAttributes();
2515 let event = document.createEvent("Events");
2516 event.initEvent("TabHide", true, false);
2517 aTab.dispatchEvent(event);
2523 <method name="selectTabAtIndex">
2524 <parameter name="aIndex"/>
2525 <parameter name="aEvent"/>
2528 let tabs = this.visibleTabs;
2530 // count backwards for aIndex < 0
2532 aIndex += tabs.length;
2534 if (aIndex >= 0 && aIndex < tabs.length)
2535 this.selectedTab = tabs[aIndex];
2538 aEvent.preventDefault();
2539 aEvent.stopPropagation();
2545 <property name="selectedTab">
2547 return this.mCurrentTab;
2552 this.mTabBox.selectedTab = val;
2558 <property name="selectedBrowser"
2559 onget="return this.mCurrentBrowser;"
2562 <property name="browsers" readonly="true">
2565 return this._browsers ||
2566 (this._browsers = Array.map(this.tabs, function (tab) tab.linkedBrowser));
2570 <field name="_browsers">null</field>
2572 <!-- Moves a tab to a new browser window, unless it's already the only tab
2573 in the current window, in which case this will do nothing. -->
2574 <method name="replaceTabWithWindow">
2575 <parameter name="aTab"/>
2576 <parameter name="aOptions"/>
2579 if (this.tabs.length == 1)
2582 let event = new CustomEvent("TabBecomingWindow", {
2586 aTab.dispatchEvent(event);
2587 if (event.defaultPrevented) {
2591 var options = "chrome,dialog=no,all";
2592 for (var name in aOptions)
2593 options += "," + name + "=" + aOptions[name];
2595 // tell a new window to take the "dropped" tab
2596 return window.openDialog(getBrowserURL(), "_blank", options, aTab);
2601 #ifdef E10S_TESTING_ONLY
2602 <!-- Opens a given tab to a non-remote window. -->
2603 <method name="openNonRemoteWindow">
2604 <parameter name="aTab"/>
2607 let url = aTab.linkedBrowser.currentURI.spec;
2608 return window.openDialog("chrome://browser/content/", "_blank", "chrome,all,dialog=no,non-remote", url);
2614 <method name="moveTabTo">
2615 <parameter name="aTab"/>
2616 <parameter name="aIndex"/>
2619 var oldPosition = aTab._tPos;
2620 if (oldPosition == aIndex)
2623 // Don't allow mixing pinned and unpinned tabs.
2625 aIndex = Math.min(aIndex, this._numPinnedTabs - 1);
2627 aIndex = Math.max(aIndex, this._numPinnedTabs);
2628 if (oldPosition == aIndex)
2631 this._lastRelatedTab = null;
2633 this.mTabFilters.splice(aIndex, 0, this.mTabFilters.splice(aTab._tPos, 1)[0]);
2634 this.mTabListeners.splice(aIndex, 0, this.mTabListeners.splice(aTab._tPos, 1)[0]);
2636 let wasFocused = (document.activeElement == this.mCurrentTab);
2638 aIndex = aIndex < aTab._tPos ? aIndex: aIndex+1;
2639 this.mCurrentTab._selected = false;
2641 // invalidate caches
2642 this._browsers = null;
2643 this._visibleTabs = null;
2645 // use .item() instead of [] because dragging to the end of the strip goes out of
2646 // bounds: .item() returns null (so it acts like appendChild), but [] throws
2647 this.tabContainer.insertBefore(aTab, this.tabs.item(aIndex));
2649 for (let i = 0; i < this.tabs.length; i++) {
2650 this.tabs[i]._tPos = i;
2651 this.tabs[i]._selected = false;
2653 this.mCurrentTab._selected = true;
2656 this.mCurrentTab.focus();
2658 this.tabContainer._handleTabSelect(false);
2661 this.tabContainer._positionPinnedTabs();
2663 this.tabContainer._setPositionalAttributes();
2665 var evt = document.createEvent("UIEvents");
2666 evt.initUIEvent("TabMove", true, false, window, oldPosition);
2667 aTab.dispatchEvent(evt);
2672 <method name="moveTabForward">
2675 let nextTab = this.mCurrentTab.nextSibling;
2676 while (nextTab && nextTab.hidden)
2677 nextTab = nextTab.nextSibling;
2680 this.moveTabTo(this.mCurrentTab, nextTab._tPos);
2681 else if (this.arrowKeysShouldWrap)
2682 this.moveTabToStart();
2687 <method name="moveTabBackward">
2690 let previousTab = this.mCurrentTab.previousSibling;
2691 while (previousTab && previousTab.hidden)
2692 previousTab = previousTab.previousSibling;
2695 this.moveTabTo(this.mCurrentTab, previousTab._tPos);
2696 else if (this.arrowKeysShouldWrap)
2697 this.moveTabToEnd();
2702 <method name="moveTabToStart">
2705 var tabPos = this.mCurrentTab._tPos;
2707 this.moveTabTo(this.mCurrentTab, 0);
2712 <method name="moveTabToEnd">
2715 var tabPos = this.mCurrentTab._tPos;
2716 if (tabPos < this.browsers.length - 1)
2717 this.moveTabTo(this.mCurrentTab, this.browsers.length - 1);
2722 <method name="moveTabOver">
2723 <parameter name="aEvent"/>
2726 var direction = window.getComputedStyle(this.parentNode, null).direction;
2727 if ((direction == "ltr" && aEvent.keyCode == KeyEvent.DOM_VK_RIGHT) ||
2728 (direction == "rtl" && aEvent.keyCode == KeyEvent.DOM_VK_LEFT))
2729 this.moveTabForward();
2731 this.moveTabBackward();
2736 <method name="duplicateTab">
2737 <parameter name="aTab"/><!-- can be from a different window as well -->
2740 return SessionStore.duplicateTab(window, aTab);
2745 <!-- BEGIN FORWARDED BROWSER PROPERTIES. IF YOU ADD A PROPERTY TO THE BROWSER ELEMENT
2746 MAKE SURE TO ADD IT HERE AS WELL. -->
2747 <property name="canGoBack"
2748 onget="return this.mCurrentBrowser.canGoBack;"
2751 <property name="canGoForward"
2752 onget="return this.mCurrentBrowser.canGoForward;"
2755 <method name="goBack">
2758 return this.mCurrentBrowser.goBack();
2763 <method name="goForward">
2766 return this.mCurrentBrowser.goForward();
2771 <method name="reload">
2774 return this.mCurrentBrowser.reload();
2779 <method name="reloadWithFlags">
2780 <parameter name="aFlags"/>
2783 return this.mCurrentBrowser.reloadWithFlags(aFlags);
2788 <method name="stop">
2791 return this.mCurrentBrowser.stop();
2796 <!-- throws exception for unknown schemes -->
2797 <method name="loadURI">
2798 <parameter name="aURI"/>
2799 <parameter name="aReferrerURI"/>
2800 <parameter name="aCharset"/>
2803 #ifdef MAKE_E10S_WORK
2804 this.updateBrowserRemotenessByURL(this.mCurrentBrowser, aURI);
2807 return this.mCurrentBrowser.loadURI(aURI, aReferrerURI, aCharset);
2808 #ifdef MAKE_E10S_WORK
2810 let url = this.mCurrentBrowser.currentURI.spec;
2811 this.updateBrowserRemotenessByURL(this.mCurrentBrowser, url);
2819 <!-- throws exception for unknown schemes -->
2820 <method name="loadURIWithFlags">
2821 <parameter name="aURI"/>
2822 <parameter name="aFlags"/>
2823 <parameter name="aReferrerURI"/>
2824 <parameter name="aCharset"/>
2825 <parameter name="aPostData"/>
2828 #ifdef MAKE_E10S_WORK
2829 this.updateBrowserRemotenessByURL(this.mCurrentBrowser, aURI);
2832 return this.mCurrentBrowser.loadURIWithFlags(aURI, aFlags, aReferrerURI, aCharset, aPostData);
2833 #ifdef MAKE_E10S_WORK
2835 let url = this.mCurrentBrowser.currentURI.spec;
2836 this.updateBrowserRemotenessByURL(this.mCurrentBrowser, url);
2844 <method name="goHome">
2847 return this.mCurrentBrowser.goHome();
2852 <property name="homePage">
2855 return this.mCurrentBrowser.homePage;
2860 this.mCurrentBrowser.homePage = val;
2866 <method name="gotoIndex">
2867 <parameter name="aIndex"/>
2870 return this.mCurrentBrowser.gotoIndex(aIndex);
2875 <method name="attachFormFill">
2877 for (var i = 0; i < this.mPanelContainer.childNodes.length; ++i) {
2878 var cb = this.getBrowserAtIndex(i);
2879 cb.attachFormFill();
2884 <method name="detachFormFill">
2886 for (var i = 0; i < this.mPanelContainer.childNodes.length; ++i) {
2887 var cb = this.getBrowserAtIndex(i);
2888 cb.detachFormFill();
2893 <property name="currentURI"
2894 onget="return this.mCurrentBrowser.currentURI;"
2897 <property name="finder"
2898 onget="return this.mCurrentBrowser.finder"
2901 <property name="docShell"
2902 onget="return this.mCurrentBrowser.docShell"
2905 <property name="webNavigation"
2906 onget="return this.mCurrentBrowser.webNavigation"
2909 <property name="webBrowserFind"
2911 onget="return this.mCurrentBrowser.webBrowserFind"/>
2913 <property name="webProgress"
2915 onget="return this.mCurrentBrowser.webProgress"/>
2917 <property name="contentWindow"
2919 onget="return this.mCurrentBrowser.contentWindow"/>
2921 <property name="sessionHistory"
2922 onget="return this.mCurrentBrowser.sessionHistory;"
2925 <property name="markupDocumentViewer"
2926 onget="return this.mCurrentBrowser.markupDocumentViewer;"
2929 <property name="contentViewerEdit"
2930 onget="return this.mCurrentBrowser.contentViewerEdit;"
2933 <property name="contentViewerFile"
2934 onget="return this.mCurrentBrowser.contentViewerFile;"
2937 <property name="contentDocument"
2938 onget="return this.mCurrentBrowser.contentDocument;"
2941 <property name="contentTitle"
2942 onget="return this.mCurrentBrowser.contentTitle;"
2945 <property name="contentPrincipal"
2946 onget="return this.mCurrentBrowser.contentPrincipal;"
2949 <property name="securityUI"
2950 onget="return this.mCurrentBrowser.securityUI;"
2953 <property name="fullZoom"
2954 onget="return this.mCurrentBrowser.fullZoom;"
2955 onset="this.mCurrentBrowser.fullZoom = val;"/>
2957 <property name="textZoom"
2958 onget="return this.mCurrentBrowser.textZoom;"
2959 onset="this.mCurrentBrowser.textZoom = val;"/>
2961 <property name="isSyntheticDocument"
2962 onget="return this.mCurrentBrowser.isSyntheticDocument;"
2965 <method name="_handleKeyDownEvent">
2966 <parameter name="aEvent"/>
2968 if (!aEvent.isTrusted) {
2969 // Don't let untrusted events mess with tabs.
2976 // Don't check if the event was already consumed because tab
2977 // navigation should always work for better user experience.
2979 if (aEvent.ctrlKey && aEvent.shiftKey && !aEvent.metaKey) {
2980 switch (aEvent.keyCode) {
2981 case aEvent.DOM_VK_PAGE_UP:
2982 this.moveTabBackward();
2983 aEvent.preventDefault();
2985 case aEvent.DOM_VK_PAGE_DOWN:
2986 this.moveTabForward();
2987 aEvent.preventDefault();
2993 if (aEvent.ctrlKey && !aEvent.shiftKey && !aEvent.metaKey &&
2994 aEvent.keyCode == KeyEvent.DOM_VK_F4 &&
2995 !this.mCurrentTab.pinned) {
2996 this.removeCurrentTab({animate: true});
2997 aEvent.preventDefault();
3003 <method name="_handleKeyPressEvent">
3004 <parameter name="aEvent"/>
3006 if (!aEvent.isTrusted) {
3007 // Don't let untrusted events mess with tabs.
3014 // We need to take care of FAYT-watching as long as the findbar
3015 // isn't initialized. The checks on aEvent are copied from
3016 // _shouldFastFind (see findbar.xml).
3017 if (!gFindBarInitialized &&
3018 !(aEvent.ctrlKey || aEvent.metaKey) &&
3019 !aEvent.defaultPrevented) {
3020 let charCode = aEvent.charCode;
3022 let char = String.fromCharCode(charCode);
3023 if (char == "'" || char == "/" ||
3024 Services.prefs.getBoolPref("accessibility.typeaheadfind")) {
3025 gFindBar._onBrowserKeypress(aEvent);
3032 if (!aEvent.metaKey)
3036 switch (aEvent.charCode) {
3037 case '}'.charCodeAt(0):
3039 case '{'.charCodeAt(0):
3040 if (window.getComputedStyle(this, null).direction == "ltr")
3042 this.tabContainer.advanceSelectedTab(offset, true);
3043 aEvent.preventDefault();
3049 <property name="userTypedClear"
3050 onget="return this.mCurrentBrowser.userTypedClear;"
3051 onset="return this.mCurrentBrowser.userTypedClear = val;"/>
3053 <property name="userTypedValue"
3054 onget="return this.mCurrentBrowser.userTypedValue;"
3055 onset="return this.mCurrentBrowser.userTypedValue = val;"/>
3057 <method name="createTooltip">
3058 <parameter name="event"/>
3060 event.stopPropagation();
3061 var tab = document.tooltipNode;
3062 if (tab.localName != "tab") {
3063 event.preventDefault();
3066 event.target.setAttribute("label", tab.mOverCloseButton ?
3067 tab.getAttribute("closetabtext") :
3068 tab.getAttribute("label"));
3072 <method name="handleEvent">
3073 <parameter name="aEvent"/>
3075 switch (aEvent.type) {
3077 this._handleKeyDownEvent(aEvent);
3080 this._handleKeyPressEvent(aEvent);
3082 case "sizemodechange":
3083 if (aEvent.target == window) {
3084 this.mCurrentBrowser.docShellIsActive =
3085 (window.windowState != window.STATE_MINIMIZED);
3092 <method name="receiveMessage">
3093 <parameter name="aMessage"/>
3095 let json = aMessage.json;
3096 let browser = aMessage.target;
3098 switch (aMessage.name) {
3099 case "DOMTitleChanged": {
3100 let tab = this._getTabForBrowser(browser);
3101 if (!tab || tab.hasAttribute("pending"))
3103 let titleChanged = this.setTabTitle(tab);
3104 if (titleChanged && !tab.selected && !tab.hasAttribute("busy"))
3105 tab.setAttribute("titlechanged", "true");
3108 case "DOMWindowClose": {
3109 if (this.tabs.length == 1) {
3114 let tab = this._getTabForBrowser(browser);
3116 this.removeTab(tab);
3120 case "contextmenu": {
3121 gContextMenuContentData = { event: aMessage.objects.event,
3123 let popup = browser.ownerDocument.getElementById("contentAreaContextMenu");
3124 let event = gContextMenuContentData.event;
3125 let pos = browser.mapScreenCoordinatesFromContent(event.screenX, event.screenY);
3126 popup.openPopupAtScreen(pos.x, pos.y, true);
3129 case "DOMWebNotificationClicked": {
3130 let tab = this._getTabForBrowser(browser);
3133 this.selectedTab = tab;
3143 let browserStack = document.getAnonymousElementByAttribute(this, "anonid", "browserStack");
3144 this.mCurrentBrowser = document.getAnonymousElementByAttribute(this, "anonid", "initialBrowser");
3146 this.mCurrentTab = this.tabContainer.firstChild;
3147 const nsIEventListenerService =
3148 Components.interfaces.nsIEventListenerService;
3149 let els = Components.classes["@mozilla.org/eventlistenerservice;1"]
3150 .getService(nsIEventListenerService);
3151 els.addSystemEventListener(document, "keydown", this, false);
3152 els.addSystemEventListener(document, "keypress", this, false);
3153 window.addEventListener("sizemodechange", this, false);
3155 var uniqueId = this._generateUniquePanelID();
3156 this.mPanelContainer.childNodes[0].id = uniqueId;
3157 this.mCurrentTab.linkedPanel = uniqueId;
3158 this.mCurrentTab._tPos = 0;
3159 this.mCurrentTab._fullyOpen = true;
3160 this.mCurrentTab.linkedBrowser = this.mCurrentBrowser;
3162 // set up the shared autoscroll popup
3163 this._autoScrollPopup = this.mCurrentBrowser._createAutoScrollPopup();
3164 this._autoScrollPopup.id = "autoscroller";
3165 this.appendChild(this._autoScrollPopup);
3166 this.mCurrentBrowser.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
3167 this.mCurrentBrowser.droppedLinkHandler = handleDroppedLink;
3168 this.updateWindowResizers();
3170 // Hook up the event listeners to the first browser
3171 var tabListener = this.mTabProgressListener(this.mCurrentTab, this.mCurrentBrowser, true);
3172 const nsIWebProgress = Components.interfaces.nsIWebProgress;
3173 const filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
3174 .createInstance(nsIWebProgress);
3175 filter.addProgressListener(tabListener, nsIWebProgress.NOTIFY_ALL);
3176 this.mTabListeners[0] = tabListener;
3177 this.mTabFilters[0] = filter;
3178 this.webProgress.addProgressListener(filter, nsIWebProgress.NOTIFY_ALL);
3180 this.style.backgroundColor =
3181 Services.prefs.getBoolPref("browser.display.use_system_colors") ?
3182 "-moz-default-background-color" :
3183 Services.prefs.getCharPref("browser.display.background_color");
3185 let remote = window.QueryInterface(Ci.nsIInterfaceRequestor)
3186 .getInterface(Ci.nsIWebNavigation)
3187 .QueryInterface(Ci.nsILoadContext)
3190 messageManager.addMessageListener("DOMTitleChanged", this);
3191 messageManager.addMessageListener("DOMWindowClose", this);
3192 messageManager.addMessageListener("contextmenu", this);
3194 // If this window has remote tabs, switch to our tabpanels fork
3195 // which does asynchronous tab switching.
3196 this.mPanelContainer.classList.add("tabbrowser-tabpanels");
3198 messageManager.addMessageListener("DOMWebNotificationClicked", this);
3202 <method name="_generateUniquePanelID">
3204 if (!this._uniquePanelIDCounter) {
3205 this._uniquePanelIDCounter = 0;
3208 let outerID = window.QueryInterface(Ci.nsIInterfaceRequestor)
3209 .getInterface(Ci.nsIDOMWindowUtils)
3212 // We want panel IDs to be globally unique, that's why we include the
3213 // window ID. We switched to a monotonic counter as Date.now() lead
3214 // to random failures because of colliding IDs.
3215 return "panel-" + outerID + "-" + (++this._uniquePanelIDCounter);
3221 for (var i = 0; i < this.mTabListeners.length; ++i) {
3222 let browser = this.getBrowserAtIndex(i);
3223 if (browser.registeredOpenURI) {
3224 this._placesAutocomplete.unregisterOpenPage(browser.registeredOpenURI);
3225 this._unifiedComplete.unregisterOpenPage(browser.registeredOpenURI);
3226 delete browser.registeredOpenURI;
3228 browser.webProgress.removeProgressListener(this.mTabFilters[i]);
3229 this.mTabFilters[i].removeProgressListener(this.mTabListeners[i]);
3230 this.mTabFilters[i] = null;
3231 this.mTabListeners[i].destroy();
3232 this.mTabListeners[i] = null;
3234 const nsIEventListenerService =
3235 Components.interfaces.nsIEventListenerService;
3236 let els = Components.classes["@mozilla.org/eventlistenerservice;1"]
3237 .getService(nsIEventListenerService);
3238 els.removeSystemEventListener(document, "keydown", this, false);
3239 els.removeSystemEventListener(document, "keypress", this, false);
3240 window.removeEventListener("sizemodechange", this, false);
3242 if (gMultiProcessBrowser) {
3243 messageManager.removeMessageListener("DOMTitleChanged", this);
3244 messageManager.removeMessageListener("contextmenu", this);
3249 <!-- Deprecated stuff, implemented for backwards compatibility. -->
3250 <method name="enterTabbedMode">
3252 Application.console.log("enterTabbedMode is an obsolete method and " +
3253 "will be removed in a future release.");
3256 <field name="mTabbedMode" readonly="true">true</field>
3257 <method name="setStripVisibilityTo">
3258 <parameter name="aShow"/>
3260 this.tabContainer.visible = aShow;
3263 <method name="getStripVisibility">
3265 return this.tabContainer.visible;
3269 <method name="_prepareForTabSwitch">
3270 <parameter name="toTab"/>
3271 <parameter name="fromTab"/>
3273 const kTabSwitchTimeout = 300;
3274 let toBrowser = this.getBrowserForTab(toTab);
3275 let fromBrowser = fromTab ? this.getBrowserForTab(fromTab)
3278 // We only want to wait for the MozAfterRemotePaint event if
3279 // the tab we're switching to is a remote tab, and if the tab
3280 // we're switching to isn't the one we've already got. The latter
3281 // case can occur when closing tabs before the currently selected
3283 let shouldWait = toBrowser.getAttribute("remote") == "true" &&
3284 toBrowser != fromBrowser;
3290 let panels = this.mPanelContainer;
3292 // Both the timeout and MozAfterPaint promises use this same
3293 // logic to determine whether they should carry out the tab
3294 // switch, or reject it outright.
3295 let attemptTabSwitch = (aResolve, aReject) => {
3296 if (this.selectedBrowser == toBrowser) {
3299 // We switched away or closed the browser before we timed
3300 // out. We reject, which will cancel the tab switch.
3305 let timeoutPromise = new Promise((aResolve, aReject) => {
3306 timeoutId = setTimeout(() => {
3307 attemptTabSwitch(aResolve, aReject);
3308 }, kTabSwitchTimeout);
3311 let paintPromise = new Promise((aResolve, aReject) => {
3312 toBrowser.addEventListener("MozAfterRemotePaint", function onRemotePaint() {
3313 toBrowser.removeEventListener("MozAfterRemotePaint", onRemotePaint);
3314 clearTimeout(timeoutId);
3315 attemptTabSwitch(aResolve, aReject);
3317 toBrowser.QueryInterface(Ci.nsIFrameLoaderOwner)
3319 .requestNotifyAfterRemotePaint();
3320 // We need to activate the docShell on the tab we're switching
3321 // to - otherwise, we won't initiate a remote paint request and
3322 // therefore we won't get the MozAfterRemotePaint event that we're
3324 // Note that this happens, as we require, even if the timeout in the
3325 // timeoutPromise triggers before the paintPromise even runs.
3326 toBrowser.docShellIsActive = true;
3329 switchPromise = Promise.race([paintPromise, timeoutPromise]);
3331 // Activate the docShell on the tab we're switching to.
3332 toBrowser.docShellIsActive = true;
3334 // No need to wait - just resolve immediately to do the switch ASAP.
3335 switchPromise = Promise.resolve();
3338 return switchPromise;
3342 <method name="_deactivateContent">
3343 <parameter name="tab"/>
3345 // It's unlikely, yet possible, that while we were waiting
3346 // to deactivate this tab, that something closed it and wiped
3347 // out the browser. For example, during a tab switch, while waiting
3348 // for the MozAfterRemotePaint event to fire, something closes the
3349 // original tab that the user had selected. If that's the case, then
3350 // there's nothing to deactivate.
3351 let browser = this.getBrowserForTab(tab);
3352 if (browser && this.selectedBrowser != browser) {
3353 browser.docShellIsActive = false;
3358 <method name="_finalizeTabSwitch">
3359 <parameter name="toTab"/>
3360 <parameter name="fromTab"/>
3362 this._adjustFocusAfterTabSwitch(toTab, fromTab);
3363 this._deactivateContent(fromTab);
3365 let toBrowser = this.getBrowserForTab(toTab);
3366 toBrowser.setAttribute("type", "content-primary");
3368 let fromBrowser = this.getBrowserForTab(fromTab);
3369 // It's possible that the tab we're switching from closed
3370 // before we were able to finalize, in which case, fromBrowser
3373 fromBrowser.setAttribute("type", "content-targetable");
3378 <method name="_cancelTabSwitch">
3379 <parameter name="toTab"/>
3381 this._deactivateContent(toTab);
3385 <property name="mContextTab" readonly="true"
3386 onget="return TabContextMenu.contextTab;"/>
3387 <property name="mPrefs" readonly="true"
3388 onget="return Services.prefs;"/>
3389 <property name="mTabContainer" readonly="true"
3390 onget="return this.tabContainer;"/>
3391 <property name="mTabs" readonly="true"
3392 onget="return this.tabs;"/>
3394 - Compatibility hack: several extensions depend on this property to
3395 - access the tab context menu or tab container, so keep that working for
3396 - now. Ideally we can remove this once extensions are using
3397 - tabbrowser.tabContextMenu and tabbrowser.tabContainer directly.
3399 <property name="mStrip" readonly="true">
3404 childNodes: [null, this.tabContextMenu, this.tabContainer],
3405 firstChild: { nextSibling: this.tabContextMenu },
3406 getElementsByAttribute: function (attr, attrValue) {
3407 if (attr == "anonid" && attrValue == "tabContextMenu")
3408 return [this.self.tabContextMenu];
3411 // Also support adding event listeners (forward to the tab container)
3412 addEventListener: function (a,b,c) { this.self.tabContainer.addEventListener(a,b,c); },
3413 removeEventListener: function (a,b,c) { this.self.tabContainer.removeEventListener(a,b,c); }
3421 <handler event="DOMWindowClose" phase="capturing">
3423 if (!event.isTrusted)
3426 if (this.tabs.length == 1)
3429 var tab = this._getTabForContentWindow(event.target);
3431 this.removeTab(tab);
3432 event.preventDefault();
3436 <handler event="DOMWillOpenModalDialog" phase="capturing">
3438 if (!event.isTrusted)
3441 // We're about to open a modal dialog, make sure the opening
3442 // tab is brought to the front.
3443 // If this is a same-process modal dialog, then we're given its DOM
3444 // window as the event's target. For remote dialogs, we're given the
3445 // browser, but that's in the originalTarget.
3446 // XXX Why originalTarget for the browser?
3447 this.selectedTab = (event.target instanceof Window) ?
3448 this._getTabForContentWindow(event.target.top) :
3449 this._getTabForBrowser(event.originalTarget);
3452 <handler event="DOMTitleChanged">
3454 if (!event.isTrusted)
3457 var contentWin = event.target.defaultView;
3458 if (contentWin != contentWin.top)
3461 var tab = this._getTabForContentWindow(contentWin);
3462 if (tab.hasAttribute("pending"))
3465 var titleChanged = this.setTabTitle(tab);
3466 if (titleChanged && !tab.selected && !tab.hasAttribute("busy"))
3467 tab.setAttribute("titlechanged", "true");
3470 <handler event="oop-browser-crashed">
3472 if (!event.isTrusted)
3475 let browser = event.originalTarget;
3476 let title = browser.contentTitle;
3477 let uri = browser.currentURI;
3478 let icon = browser.mIconURL;
3480 this.updateBrowserRemotenessByURL(browser, "about:tabcrashed");
3482 browser.setAttribute("crashedPageTitle", title);
3483 browser.docShell.displayLoadError(Cr.NS_ERROR_CONTENT_CRASHED, uri, null);
3484 browser.removeAttribute("crashedPageTitle");
3485 let tab = this._getTabForBrowser(browser);
3486 this.setIcon(tab, icon);
3492 <binding id="tabbrowser-tabbox"
3493 extends="chrome://global/content/bindings/tabbox.xml#tabbox">
3495 <property name="tabs" readonly="true"
3496 onget="return document.getBindingParent(this).tabContainer;"/>
3500 <binding id="tabbrowser-arrowscrollbox" extends="chrome://global/content/bindings/scrollbox.xml#arrowscrollbox-clicktoscroll">
3502 <!-- Override scrollbox.xml method, since our scrollbox's children are
3503 inherited from the binding parent -->
3504 <method name="_getScrollableElements">
3506 return Array.filter(document.getBindingParent(this).childNodes,
3507 this._canScrollToElement, this);
3510 <method name="_canScrollToElement">
3511 <parameter name="tab"/>
3513 return !tab.pinned && !tab.hidden;
3516 <field name="_tabMarginLeft">null</field>
3517 <field name="_tabMarginRight">null</field>
3518 <method name="_calcTabMargins">
3519 <parameter name="aTab"/>
3521 if (this._tabMarginLeft === null || this._tabMarginRight === null) {
3522 let tabMiddle = document.getAnonymousElementByAttribute(aTab, "class", "tab-background-middle");
3523 let tabMiddleStyle = window.getComputedStyle(tabMiddle, null);
3524 this._tabMarginLeft = parseFloat(tabMiddleStyle.marginLeft);
3525 this._tabMarginRight = parseFloat(tabMiddleStyle.marginRight);
3529 <method name="_adjustElementStartAndEnd">
3530 <parameter name="aTab"/>
3531 <parameter name="tabStart"/>
3532 <parameter name="tabEnd"/>
3534 this._calcTabMargins(aTab);
3535 if (this._tabMarginLeft < 0) {
3536 tabStart = tabStart + this._tabMarginLeft;
3538 if (this._tabMarginRight < 0) {
3539 tabEnd = tabEnd - this._tabMarginRight;
3541 return [tabStart, tabEnd];
3547 <handler event="underflow" phase="capturing"><![CDATA[
3548 if (event.detail == 0)
3549 return; // Ignore vertical events
3551 var tabs = document.getBindingParent(this);
3552 tabs.removeAttribute("overflow");
3554 if (tabs._lastTabClosedByMouse)
3555 tabs._expandSpacerBy(this._scrollButtonDown.clientWidth);
3557 tabs.tabbrowser._removingTabs.forEach(tabs.tabbrowser.removeTab,
3560 tabs._positionPinnedTabs();
3562 <handler event="overflow"><![CDATA[
3563 if (event.detail == 0)
3564 return; // Ignore vertical events
3566 var tabs = document.getBindingParent(this);
3567 tabs.setAttribute("overflow", "true");
3568 tabs._positionPinnedTabs();
3569 tabs._handleTabSelect(false);
3574 <binding id="tabbrowser-tabs"
3575 extends="chrome://global/content/bindings/tabbox.xml#tabs">
3577 <stylesheet src="chrome://browser/content/tabbrowser.css"/>
3581 <xul:hbox align="end">
3582 <xul:image class="tab-drop-indicator" anonid="tab-drop-indicator" collapsed="true"/>
3584 <xul:arrowscrollbox anonid="arrowscrollbox" orient="horizontal" flex="1"
3585 style="min-width: 1px;"
3587 clicktoscroll="true"
3589 class="tabbrowser-arrowscrollbox">
3590 # This is a hack to circumvent bug 472020, otherwise the tabs show up on the
3591 # right of the newtab button.
3592 <children includes="tab"/>
3593 # This is to ensure anything extensions put here will go before the newtab
3594 # button, necessary due to the previous hack.
3596 <xul:toolbarbutton class="tabs-newtab-button"
3597 anonid="tabs-newtab-button"
3598 command="cmd_newNavigatorTab"
3599 onclick="checkForMiddleClick(this, event);"
3600 onmouseover="document.getBindingParent(this)._enterNewTab();"
3601 onmouseout="document.getBindingParent(this)._leaveNewTab();"
3602 tooltip="dynamic-shortcut-tooltip"/>
3603 <xul:spacer class="closing-tabs-spacer" anonid="closing-tabs-spacer"
3605 </xul:arrowscrollbox>
3608 <implementation implements="nsIDOMEventListener">
3611 this.mTabClipWidth = Services.prefs.getIntPref("browser.tabs.tabClipWidth");
3613 var tab = this.firstChild;
3614 tab.label = this.tabbrowser.mStringBundle.getString("tabs.emptyTabTitle");
3615 tab.setAttribute("crop", "end");
3616 tab.setAttribute("onerror", "this.removeAttribute('image');");
3618 window.addEventListener("resize", this, false);
3619 window.addEventListener("load", this, false);
3622 this._tabAnimationLoggingEnabled = Services.prefs.getBoolPref("browser.tabs.animationLogging.enabled");
3624 this._tabAnimationLoggingEnabled = false;
3626 this._browserNewtabpageEnabled = Services.prefs.getBoolPref("browser.newtabpage.enabled");
3630 <field name="tabbrowser" readonly="true">
3631 document.getElementById(this.getAttribute("tabbrowser"));
3634 <field name="tabbox" readonly="true">
3635 this.tabbrowser.mTabBox;
3638 <field name="contextMenu" readonly="true">
3639 document.getElementById("tabContextMenu");
3642 <field name="mTabstripWidth">0</field>
3644 <field name="mTabstrip">
3645 document.getAnonymousElementByAttribute(this, "anonid", "arrowscrollbox");
3648 <field name="_firstTab">null</field>
3649 <field name="_lastTab">null</field>
3650 <field name="_afterSelectedTab">null</field>
3651 <field name="_beforeHoveredTab">null</field>
3652 <field name="_afterHoveredTab">null</field>
3653 <field name="_hoveredTab">null</field>
3655 <property name="_isCustomizing" readonly="true">
3657 let root = document.documentElement;
3658 return root.getAttribute("customizing") == "true" ||
3659 root.getAttribute("customize-exiting") == "true";
3663 <method name="_setPositionalAttributes">
3665 let visibleTabs = this.tabbrowser.visibleTabs;
3667 if (!visibleTabs.length)
3670 let selectedIndex = visibleTabs.indexOf(this.selectedItem);
3672 let lastVisible = visibleTabs.length - 1;
3674 if (this._afterSelectedTab)
3675 this._afterSelectedTab.removeAttribute("afterselected-visible");
3676 if (this.selectedItem.closing || selectedIndex == lastVisible) {
3677 this._afterSelectedTab = null;
3679 this._afterSelectedTab = visibleTabs[selectedIndex + 1];
3680 this._afterSelectedTab.setAttribute("afterselected-visible",
3685 this._firstTab.removeAttribute("first-visible-tab");
3686 this._firstTab = visibleTabs[0];
3687 this._firstTab.setAttribute("first-visible-tab", "true");
3689 this._lastTab.removeAttribute("last-visible-tab");
3690 this._lastTab = visibleTabs[lastVisible];
3691 this._lastTab.setAttribute("last-visible-tab", "true");
3693 let hoveredTab = this._hoveredTab;
3695 hoveredTab._mouseleave();
3696 hoveredTab._mouseenter();
3701 <field name="_blockDblClick">false</field>
3703 <field name="_tabDropIndicator">
3704 document.getAnonymousElementByAttribute(this, "anonid", "tab-drop-indicator");
3707 <field name="_dragOverDelay">350</field>
3708 <field name="_dragTime">0</field>
3710 <field name="_container" readonly="true"><![CDATA[
3711 this.parentNode && this.parentNode.localName == "toolbar" ? this.parentNode : this;
3714 <field name="_propagatedVisibilityOnce">false</field>
3716 <property name="visible"
3717 onget="return !this._container.collapsed;">
3719 if (val == this.visible &&
3720 this._propagatedVisibilityOnce)
3723 this._container.collapsed = !val;
3725 this._propagateVisibility();
3726 this._propagatedVisibilityOnce = true;
3732 <method name="_enterNewTab">
3734 let visibleTabs = this.tabbrowser.visibleTabs;
3735 let candidate = visibleTabs[visibleTabs.length - 1];
3736 if (!candidate.selected) {
3737 this._beforeHoveredTab = candidate;
3738 candidate.setAttribute("beforehovered", "true");
3743 <method name="_leaveNewTab">
3745 if (this._beforeHoveredTab) {
3746 this._beforeHoveredTab.removeAttribute("beforehovered");
3747 this._beforeHoveredTab = null;
3752 <method name="_propagateVisibility">
3754 let visible = this.visible;
3756 document.getElementById("menu_closeWindow").hidden = !visible;
3757 document.getElementById("menu_close").setAttribute("label",
3758 this.tabbrowser.mStringBundle.getString(visible ? "tabs.closeTab" : "tabs.close"));
3760 TabsInTitlebar.allowedBy("tabs-visible", visible);
3764 <method name="updateVisibility">
3766 if (this.childNodes.length - this.tabbrowser._removingTabs.length == 1)
3767 this.visible = window.toolbar.visible;
3769 this.visible = true;
3773 <method name="adjustTabstrip">
3775 let numTabs = this.childNodes.length -
3776 this.tabbrowser._removingTabs.length;
3778 // This is an optimization to avoid layout flushes by calling
3779 // getBoundingClientRect() when we just opened a second tab. In
3780 // this case it's highly unlikely that the tab width is smaller
3781 // than mTabClipWidth and the tab close button obscures too much
3782 // of the tab's label. In the edge case of the window being too
3783 // narrow (or if tabClipWidth has been set to a way higher value),
3784 // we'll correct the 'closebuttons' attribute after the tabopen
3785 // animation has finished.
3787 let tab = this.tabbrowser.visibleTabs[this.tabbrowser._numPinnedTabs];
3788 if (tab && tab.getBoundingClientRect().width <= this.mTabClipWidth) {
3789 this.setAttribute("closebuttons", "activetab");
3793 this.removeAttribute("closebuttons");
3797 <method name="_handleTabSelect">
3798 <parameter name="aSmoothScroll"/>
3800 if (this.getAttribute("overflow") == "true")
3801 this.mTabstrip.ensureElementIsVisible(this.selectedItem, aSmoothScroll);
3805 <method name="_fillTrailingGap">
3808 // if we're at the right side (and not the logical end,
3809 // which is why this works for both LTR and RTL)
3810 // of the tabstrip, we need to ensure that we stay
3811 // completely scrolled to the right side
3812 var tabStrip = this.mTabstrip;
3813 if (tabStrip.scrollPosition + tabStrip.scrollClientSize >
3814 tabStrip.scrollSize)
3815 tabStrip.scrollByPixels(-1);
3820 <field name="_closingTabsSpacer">
3821 document.getAnonymousElementByAttribute(this, "anonid", "closing-tabs-spacer");
3824 <field name="_tabDefaultMaxWidth">NaN</field>
3825 <field name="_lastTabClosedByMouse">false</field>
3826 <field name="_hasTabTempMaxWidth">false</field>
3828 <!-- Try to keep the active tab's close button under the mouse cursor -->
3829 <method name="_lockTabSizing">
3830 <parameter name="aTab"/>
3832 var tabs = this.tabbrowser.visibleTabs;
3836 var isEndTab = (aTab._tPos > tabs[tabs.length-1]._tPos);
3837 var tabWidth = aTab.getBoundingClientRect().width;
3839 if (!this._tabDefaultMaxWidth)
3840 this._tabDefaultMaxWidth =
3841 parseFloat(window.getComputedStyle(aTab).maxWidth);
3842 this._lastTabClosedByMouse = true;
3844 if (this.getAttribute("overflow") == "true") {
3845 // Don't need to do anything if we're in overflow mode and aren't scrolled
3846 // all the way to the right, or if we're closing the last tab.
3847 if (isEndTab || !this.mTabstrip._scrollButtonDown.disabled)
3850 // If the tab has an owner that will become the active tab, the owner will
3851 // be to the left of it, so we actually want the left tab to slide over.
3852 // This can't be done as easily in non-overflow mode, so we don't bother.
3856 this._expandSpacerBy(tabWidth);
3857 } else { // non-overflow mode
3858 // Locking is neither in effect nor needed, so let tabs expand normally.
3859 if (isEndTab && !this._hasTabTempMaxWidth)
3862 let numPinned = this.tabbrowser._numPinnedTabs;
3863 // Force tabs to stay the same width, unless we're closing the last tab,
3864 // which case we need to let them expand just enough so that the overall
3865 // tabbar width is the same.
3867 let numNormalTabs = tabs.length - numPinned;
3868 tabWidth = tabWidth * (numNormalTabs + 1) / numNormalTabs;
3869 if (tabWidth > this._tabDefaultMaxWidth)
3870 tabWidth = this._tabDefaultMaxWidth;
3873 for (let i = numPinned; i < tabs.length; i++) {
3875 tab.style.setProperty("max-width", tabWidth, "important");
3876 if (!isEndTab) { // keep tabs the same width
3877 tab.style.transition = "none";
3878 tab.clientTop; // flush styles to skip animation; see bug 649247
3879 tab.style.transition = "";
3882 this._hasTabTempMaxWidth = true;
3883 this.tabbrowser.addEventListener("mousemove", this, false);
3884 window.addEventListener("mouseout", this, false);
3889 <method name="_expandSpacerBy">
3890 <parameter name="pixels"/>
3892 let spacer = this._closingTabsSpacer;
3893 spacer.style.width = parseFloat(spacer.style.width) + pixels + "px";
3894 this.setAttribute("using-closing-tabs-spacer", "true");
3895 this.tabbrowser.addEventListener("mousemove", this, false);
3896 window.addEventListener("mouseout", this, false);
3900 <method name="_unlockTabSizing">
3902 this.tabbrowser.removeEventListener("mousemove", this, false);
3903 window.removeEventListener("mouseout", this, false);
3905 if (this._hasTabTempMaxWidth) {
3906 this._hasTabTempMaxWidth = false;
3907 let tabs = this.tabbrowser.visibleTabs;
3908 for (let i = 0; i < tabs.length; i++)
3909 tabs[i].style.maxWidth = "";
3912 if (this.hasAttribute("using-closing-tabs-spacer")) {
3913 this.removeAttribute("using-closing-tabs-spacer");
3914 this._closingTabsSpacer.style.width = 0;
3919 <field name="_lastNumPinned">0</field>
3920 <method name="_positionPinnedTabs">
3922 var numPinned = this.tabbrowser._numPinnedTabs;
3923 var doPosition = this.getAttribute("overflow") == "true" &&
3927 this.setAttribute("positionpinnedtabs", "true");
3929 let scrollButtonWidth = this.mTabstrip._scrollButtonDown.getBoundingClientRect().width;
3930 let paddingStart = this.mTabstrip.scrollboxPaddingStart;
3933 for (let i = numPinned - 1; i >= 0; i--) {
3934 let tab = this.childNodes[i];
3935 width += tab.getBoundingClientRect().width;
3936 tab.style.MozMarginStart = - (width + scrollButtonWidth + paddingStart) + "px";
3939 this.style.MozPaddingStart = width + paddingStart + "px";
3942 this.removeAttribute("positionpinnedtabs");
3944 for (let i = 0; i < numPinned; i++) {
3945 let tab = this.childNodes[i];
3946 tab.style.MozMarginStart = "";
3949 this.style.MozPaddingStart = "";
3952 if (this._lastNumPinned != numPinned) {
3953 this._lastNumPinned = numPinned;
3954 this._handleTabSelect(false);
3959 <method name="_animateTabMove">
3960 <parameter name="event"/>
3962 let draggedTab = event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0);
3964 if (this.getAttribute("movingtab") != "true") {
3965 this.setAttribute("movingtab", "true");
3966 this.selectedItem = draggedTab;
3969 if (!("animLastScreenX" in draggedTab._dragData))
3970 draggedTab._dragData.animLastScreenX = draggedTab._dragData.screenX;
3972 let screenX = event.screenX;
3973 if (screenX == draggedTab._dragData.animLastScreenX)
3976 let draggingRight = screenX > draggedTab._dragData.animLastScreenX;
3977 draggedTab._dragData.animLastScreenX = screenX;
3979 let rtl = (window.getComputedStyle(this).direction == "rtl");
3980 let pinned = draggedTab.pinned;
3981 let numPinned = this.tabbrowser._numPinnedTabs;
3982 let tabs = this.tabbrowser.visibleTabs
3983 .slice(pinned ? 0 : numPinned,
3984 pinned ? numPinned : undefined);
3987 let tabWidth = draggedTab.getBoundingClientRect().width;
3989 // Move the dragged tab based on the mouse position.
3991 let leftTab = tabs[0];
3992 let rightTab = tabs[tabs.length - 1];
3993 let tabScreenX = draggedTab.boxObject.screenX;
3994 let translateX = screenX - draggedTab._dragData.screenX;
3996 translateX += this.mTabstrip.scrollPosition - draggedTab._dragData.scrollX;
3997 let leftBound = leftTab.boxObject.screenX - tabScreenX;
3998 let rightBound = (rightTab.boxObject.screenX + rightTab.boxObject.width) -
3999 (tabScreenX + tabWidth);
4000 translateX = Math.max(translateX, leftBound);
4001 translateX = Math.min(translateX, rightBound);
4002 draggedTab.style.transform = "translateX(" + translateX + "px)";
4004 // Determine what tab we're dragging over.
4005 // * Point of reference is the center of the dragged tab. If that
4006 // point touches a background tab, the dragged tab would take that
4007 // tab's position when dropped.
4008 // * We're doing a binary search in order to reduce the amount of
4009 // tabs we need to check.
4011 let tabCenter = tabScreenX + translateX + tabWidth / 2;
4013 let oldIndex = "animDropIndex" in draggedTab._dragData ?
4014 draggedTab._dragData.animDropIndex : draggedTab._tPos;
4016 let high = tabs.length - 1;
4017 while (low <= high) {
4018 let mid = Math.floor((low + high) / 2);
4019 if (tabs[mid] == draggedTab &&
4022 let boxObject = tabs[mid].boxObject;
4023 let screenX = boxObject.screenX + getTabShift(tabs[mid], oldIndex);
4024 if (screenX > tabCenter) {
4026 } else if (screenX + boxObject.width < tabCenter) {
4029 newIndex = tabs[mid]._tPos;
4033 if (newIndex >= oldIndex)
4035 if (newIndex < 0 || newIndex == oldIndex)
4037 draggedTab._dragData.animDropIndex = newIndex;
4039 // Shift background tabs to leave a gap where the dragged tab
4040 // would currently be dropped.
4042 for (let tab of tabs) {
4043 if (tab != draggedTab) {
4044 let shift = getTabShift(tab, newIndex);
4045 tab.style.transform = shift ? "translateX(" + shift + "px)" : "";
4049 function getTabShift(tab, dropIndex) {
4050 if (tab._tPos < draggedTab._tPos && tab._tPos >= dropIndex)
4051 return rtl ? -tabWidth : tabWidth;
4052 if (tab._tPos > draggedTab._tPos && tab._tPos < dropIndex)
4053 return rtl ? tabWidth : -tabWidth;
4059 <method name="_finishAnimateTabMove">
4061 if (this.getAttribute("movingtab") != "true")
4064 for (let tab of this.tabbrowser.visibleTabs)
4065 tab.style.transform = "";
4067 this.removeAttribute("movingtab");
4069 this._handleTabSelect();
4073 <method name="handleEvent">
4074 <parameter name="aEvent"/>
4076 switch (aEvent.type) {
4078 this.updateVisibility();
4081 if (aEvent.target != window)
4084 TabsInTitlebar.updateAppearance();
4086 var width = this.mTabstrip.boxObject.width;
4087 if (width != this.mTabstripWidth) {
4088 this.adjustTabstrip();
4089 this._fillTrailingGap();
4090 this._handleTabSelect();
4091 this.mTabstripWidth = width;
4094 this.tabbrowser.updateWindowResizers();
4097 // If the "related target" (the node to which the pointer went) is not
4098 // a child of the current document, the mouse just left the window.
4099 let relatedTarget = aEvent.relatedTarget;
4100 if (relatedTarget && relatedTarget.ownerDocument == document)
4103 if (document.getElementById("tabContextMenu").state != "open")
4104 this._unlockTabSizing();
4110 <field name="_animateElement">
4111 this.mTabstrip._scrollButtonDown;
4114 <method name="_notifyBackgroundTab">
4115 <parameter name="aTab"/>
4120 var scrollRect = this.mTabstrip.scrollClientRect;
4121 var tab = aTab.getBoundingClientRect();
4122 this.mTabstrip._calcTabMargins(aTab);
4124 // DOMRect left/right properties are immutable.
4125 tab = {left: tab.left, right: tab.right};
4127 // Is the new tab already completely visible?
4128 if (scrollRect.left <= tab.left && tab.right <= scrollRect.right)
4131 if (this.mTabstrip.smoothScroll) {
4132 let selected = !this.selectedItem.pinned &&
4133 this.selectedItem.getBoundingClientRect();
4135 selected = {left: selected.left, right: selected.right};
4136 // Need to take in to account the width of the left/right margins on tabs.
4137 selected.left = selected.left + this.mTabstrip._tabMarginLeft;
4138 selected.right = selected.right - this.mTabstrip._tabMarginRight;
4141 tab.left += this.mTabstrip._tabMarginLeft;
4142 tab.right -= this.mTabstrip._tabMarginRight;
4144 // Can we make both the new tab and the selected tab completely visible?
4146 Math.max(tab.right - selected.left, selected.right - tab.left) <=
4148 this.mTabstrip.ensureElementIsVisible(aTab);
4152 this.mTabstrip._smoothScrollByPixels(this.mTabstrip._isRTLScrollbox ?
4153 selected.right - scrollRect.right :
4154 selected.left - scrollRect.left);
4157 if (!this._animateElement.hasAttribute("notifybgtab")) {
4158 this._animateElement.setAttribute("notifybgtab", "true");
4159 setTimeout(function (ele) {
4160 ele.removeAttribute("notifybgtab");
4161 }, 150, this._animateElement);
4166 <method name="_getDragTargetTab">
4167 <parameter name="event"/>
4169 let tab = event.target.localName == "tab" ? event.target : null;
4171 (event.type == "drop" || event.type == "dragover") &&
4172 event.dataTransfer.dropEffect == "link") {
4173 let boxObject = tab.boxObject;
4174 if (event.screenX < boxObject.screenX + boxObject.width * .25 ||
4175 event.screenX > boxObject.screenX + boxObject.width * .75)
4182 <method name="_getDropIndex">
4183 <parameter name="event"/>
4185 var tabs = this.childNodes;
4186 var tab = this._getDragTargetTab(event);
4187 if (window.getComputedStyle(this, null).direction == "ltr") {
4188 for (let i = tab ? tab._tPos : 0; i < tabs.length; i++)
4189 if (event.screenX < tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2)
4192 for (let i = tab ? tab._tPos : 0; i < tabs.length; i++)
4193 if (event.screenX > tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2)
4200 <method name="_setEffectAllowedForDataTransfer">
4201 <parameter name="event"/>
4203 var dt = event.dataTransfer;
4204 // Disallow dropping multiple items
4205 if (dt.mozItemCount > 1)
4206 return dt.effectAllowed = "none";
4208 var types = dt.mozTypesAt(0);
4209 var sourceNode = null;
4210 // tabs are always added as the first type
4211 if (types[0] == TAB_DROP_TYPE) {
4212 var sourceNode = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
4213 if (sourceNode instanceof XULElement &&
4214 sourceNode.localName == "tab" &&
4215 sourceNode.ownerDocument.defaultView instanceof ChromeWindow &&
4216 sourceNode.ownerDocument.documentElement.getAttribute("windowtype") == "navigator:browser" &&
4217 sourceNode.ownerDocument.defaultView.gBrowser.tabContainer == sourceNode.parentNode) {
4218 // Do not allow transfering a private tab to a non-private window
4220 if (PrivateBrowsingUtils.isWindowPrivate(window) !=
4221 PrivateBrowsingUtils.isWindowPrivate(sourceNode.ownerDocument.defaultView))
4222 return dt.effectAllowed = "none";
4224 if (window.gMultiProcessBrowser !=
4225 sourceNode.ownerDocument.defaultView.gMultiProcessBrowser)
4226 return dt.effectAllowed = "none";
4229 return dt.effectAllowed = event.altKey ? "copy" : "move";
4231 return dt.effectAllowed = event.ctrlKey ? "copy" : "move";
4236 if (browserDragAndDrop.canDropLink(event)) {
4237 // Here we need to do this manually
4238 return dt.effectAllowed = dt.dropEffect = "link";
4240 return dt.effectAllowed = "none";
4244 <method name="_handleNewTab">
4245 <parameter name="tab"/>
4247 if (tab.parentNode != this)
4249 tab._fullyOpen = true;
4251 this.adjustTabstrip();
4253 if (tab.getAttribute("selected") == "true") {
4254 this._fillTrailingGap();
4255 this._handleTabSelect();
4257 this._notifyBackgroundTab(tab);
4260 // XXXmano: this is a temporary workaround for bug 345399
4261 // We need to manually update the scroll buttons disabled state
4262 // if a tab was inserted to the overflow area or removed from it
4263 // without any scrolling and when the tabbar has already
4265 this.mTabstrip._updateScrollButtonsDisabledState();
4269 <method name="_canAdvanceToTab">
4270 <parameter name="aTab"/>
4273 return !aTab.closing;
4278 <method name="_handleTabTelemetryStart">
4279 <parameter name="aTab"/>
4280 <parameter name="aURI"/>
4283 // Animation-smoothness telemetry/logging
4284 if (Services.telemetry.canRecord || this._tabAnimationLoggingEnabled) {
4285 if (aURI == "about:newtab" && (aTab._tPos == 1 || aTab._tPos == 2)) {
4286 // Indicate newtab page animation where other tabs are unaffected
4287 // (for which case, the 2nd or 3rd tabs are good representatives, even if not absolute)
4288 aTab._recordingTabOpenPlain = true;
4290 aTab._recordingHandle = window.QueryInterface(Ci.nsIInterfaceRequestor)
4291 .getInterface(Ci.nsIDOMWindowUtils)
4292 .startFrameTimeRecording();
4295 // Overall animation duration
4296 aTab._animStartTime = Date.now();
4301 <method name="_handleTabTelemetryEnd">
4302 <parameter name="aTab"/>
4305 if (!aTab._animStartTime) {
4309 Services.telemetry.getHistogramById(aTab.closing ?
4310 "FX_TAB_ANIM_CLOSE_MS" :
4311 "FX_TAB_ANIM_OPEN_MS")
4312 .add(Date.now() - aTab._animStartTime);
4313 aTab._animStartTime = 0;
4315 // Handle tab animation smoothness telemetry/logging of frame intervals and paint times
4316 if (!("_recordingHandle" in aTab)) {
4320 let intervals = window.QueryInterface(Ci.nsIInterfaceRequestor)
4321 .getInterface(Ci.nsIDOMWindowUtils)
4322 .stopFrameTimeRecording(aTab._recordingHandle);
4323 delete aTab._recordingHandle;
4324 let frameCount = intervals.length;
4326 if (this._tabAnimationLoggingEnabled) {
4327 let msg = "Tab " + (aTab.closing ? "close" : "open") + " (Frame-interval):\n";
4328 for (let i = 0; i < frameCount; i++) {
4329 msg += Math.round(intervals[i]) + "\n";
4331 Services.console.logStringMessage(msg);
4334 // For telemetry, the first frame interval is not useful since it may represent an interval
4335 // to a relatively old frame (prior to recording start). So we'll ignore it for the average.
4336 if (frameCount > 1) {
4337 let averageInterval = 0;
4338 for (let i = 1; i < frameCount; i++) {
4339 averageInterval += intervals[i];
4341 averageInterval = averageInterval / (frameCount - 1);
4343 Services.telemetry.getHistogramById("FX_TAB_ANIM_ANY_FRAME_INTERVAL_MS").add(averageInterval);
4345 if (aTab._recordingTabOpenPlain) {
4346 delete aTab._recordingTabOpenPlain;
4347 // While we do have a telemetry probe NEWTAB_PAGE_ENABLED to monitor newtab preview, it'll be
4348 // easier to overview the data without slicing by it. Hence the additional histograms with _PREVIEW.
4349 let preview = this._browserNewtabpageEnabled ? "_PREVIEW" : "";
4350 Services.telemetry.getHistogramById("FX_TAB_ANIM_OPEN" + preview + "_FRAME_INTERVAL_MS").add(averageInterval);
4357 <!-- Deprecated stuff, implemented for backwards compatibility. -->
4358 <property name="mAllTabsPopup" readonly="true"
4359 onget="return document.getElementById('alltabs-popup');"/>
4363 <handler event="TabSelect" action="this._handleTabSelect();"/>
4365 <handler event="transitionend"><![CDATA[
4366 if (event.propertyName != "max-width")
4369 var tab = event.target;
4371 this._handleTabTelemetryEnd(tab);
4373 if (tab.getAttribute("fadein") == "true") {
4375 this.adjustTabstrip();
4377 this._handleNewTab(tab);
4378 } else if (tab.closing) {
4379 this.tabbrowser._endRemoveTab(tab);
4383 <handler event="dblclick"><![CDATA[
4385 // When the tabbar has an unified appearance with the titlebar
4386 // and menubar, a double-click in it should have the same behavior
4387 // as double-clicking the titlebar
4388 if (TabsInTitlebar.enabled || this.parentNode._dragBindingAlive)
4392 if (event.button != 0 ||
4393 event.originalTarget.localName != "box")
4396 // See hack note in the tabbrowser-close-tab-button binding
4397 if (!this._blockDblClick)
4400 event.preventDefault();
4403 <handler event="click" button="0" phase="capturing"><![CDATA[
4404 /* Catches extra clicks meant for the in-tab close button.
4405 * Placed here to avoid leaking (a temporary handler added from the
4406 * in-tab close button binding would close over the tab and leak it
4407 * until the handler itself was removed). (bug 897751)
4409 * The only sequence in which a second click event (i.e. dblclik)
4410 * can be dispatched on an in-tab close button is when it is shown
4411 * after the first click (i.e. the first click event was dispatched
4412 * on the tab). This happens when we show the close button only on
4413 * the active tab. (bug 352021)
4414 * The only sequence in which a third click event can be dispatched
4415 * on an in-tab close button is when the tab was opened with a
4416 * double click on the tabbar. (bug 378344)
4417 * In both cases, it is most likely that the close button area has
4418 * been accidentally clicked, therefore we do not close the tab.
4420 * We don't want to ignore processing of more than one click event,
4421 * though, since the user might actually be repeatedly clicking to
4422 * close many tabs at once.
4424 let target = event.originalTarget;
4425 if (target.classList.contains('tab-close-button')) {
4426 // We preemptively set this to allow the closing-multiple-tabs-
4428 if (this._blockDblClick) {
4429 target._ignoredCloseButtonClicks = true;
4430 } else if (event.detail > 1 && !target._ignoredCloseButtonClicks) {
4431 target._ignoredCloseButtonClicks = true;
4432 event.stopPropagation();
4435 // Reset the "ignored click" flag
4436 target._ignoredCloseButtonClicks = false;
4440 /* Protects from close-tab-button errant doubleclick:
4441 * Since we're removing the event target, if the user
4442 * double-clicks the button, the dblclick event will be dispatched
4443 * with the tabbar as its event target (and explicit/originalTarget),
4444 * which treats that as a mouse gesture for opening a new tab.
4445 * In this context, we're manually blocking the dblclick event
4446 * (see tabbrowser-close-tab-button dblclick handler).
4448 if (this._blockDblClick) {
4449 if (!("_clickedTabBarOnce" in this)) {
4450 this._clickedTabBarOnce = true;
4453 delete this._clickedTabBarOnce;
4454 this._blockDblClick = false;
4458 <handler event="click"><![CDATA[
4459 if (event.button != 1)
4462 if (event.target.localName == "tab") {
4463 this.tabbrowser.removeTab(event.target, {animate: true, byMouse: true});
4464 } else if (event.originalTarget.localName == "box") {
4470 event.stopPropagation();
4473 <handler event="keydown" group="system"><![CDATA[
4474 if (event.altKey || event.shiftKey ||
4478 !event.ctrlKey || event.metaKey)
4482 // Don't check if the event was already consumed because tab navigation
4483 // should work always for better user experience.
4485 switch (event.keyCode) {
4486 case KeyEvent.DOM_VK_UP:
4487 this.tabbrowser.moveTabBackward();
4489 case KeyEvent.DOM_VK_DOWN:
4490 this.tabbrowser.moveTabForward();
4492 case KeyEvent.DOM_VK_RIGHT:
4493 case KeyEvent.DOM_VK_LEFT:
4494 this.tabbrowser.moveTabOver(event);
4496 case KeyEvent.DOM_VK_HOME:
4497 this.tabbrowser.moveTabToStart();
4499 case KeyEvent.DOM_VK_END:
4500 this.tabbrowser.moveTabToEnd();
4503 // Consume the keydown event for the above keyboard
4507 event.preventDefault();
4510 <handler event="dragstart"><![CDATA[
4511 var tab = this._getDragTargetTab(event);
4512 if (!tab || this._isCustomizing)
4515 let dt = event.dataTransfer;
4516 dt.mozSetDataAt(TAB_DROP_TYPE, tab, 0);
4517 let browser = tab.linkedBrowser;
4519 // We must not set text/x-moz-url or text/plain data here,
4520 // otherwise trying to deatch the tab by dropping it on the desktop
4521 // may result in an "internet shortcut"
4522 dt.mozSetDataAt("text/x-moz-text-internal", browser.currentURI.spec, 0);
4524 // Set the cursor to an arrow during tab drags.
4525 dt.mozCursor = "default";
4527 // Create a canvas to which we capture the current tab.
4528 // Until canvas is HiDPI-aware (bug 780362), we need to scale the desired
4529 // canvas size (in CSS pixels) to the window's backing resolution in order
4530 // to get a full-resolution drag image for use on HiDPI displays.
4531 let windowUtils = window.getInterface(Ci.nsIDOMWindowUtils);
4532 let scale = windowUtils.screenPixelsPerCSSPixel / windowUtils.fullZoom;
4533 let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
4534 canvas.mozOpaque = true;
4535 canvas.width = 160 * scale;
4536 canvas.height = 90 * scale;
4537 if (!gMultiProcessBrowser) {
4538 // Bug 863512 - Make page thumbnails work in e10s
4539 PageThumbs.captureToCanvas(browser.contentWindow, canvas);
4541 dt.setDragImage(canvas, -16 * scale, -16 * scale);
4543 // _dragData.offsetX/Y give the coordinates that the mouse should be
4544 // positioned relative to the corner of the new window created upon
4545 // dragend such that the mouse appears to have the same position
4546 // relative to the corner of the dragged tab.
4547 function clientX(ele) ele.getBoundingClientRect().left;
4548 let tabOffsetX = clientX(tab) - clientX(this);
4550 offsetX: event.screenX - window.screenX - tabOffsetX,
4551 offsetY: event.screenY - window.screenY,
4552 scrollX: this.mTabstrip.scrollPosition,
4553 screenX: event.screenX
4556 event.stopPropagation();
4559 <handler event="dragover"><![CDATA[
4560 var effects = this._setEffectAllowedForDataTransfer(event);
4562 var ind = this._tabDropIndicator;
4563 if (effects == "" || effects == "none") {
4564 ind.collapsed = true;
4567 event.preventDefault();
4568 event.stopPropagation();
4570 var tabStrip = this.mTabstrip;
4571 var ltr = (window.getComputedStyle(this, null).direction == "ltr");
4573 // autoscroll the tab strip if we drag over the scroll
4574 // buttons, even if we aren't dragging a tab, but then
4575 // return to avoid drawing the drop indicator
4576 var pixelsToScroll = 0;
4577 if (this.getAttribute("overflow") == "true") {
4578 var targetAnonid = event.originalTarget.getAttribute("anonid");
4579 switch (targetAnonid) {
4580 case "scrollbutton-up":
4581 pixelsToScroll = tabStrip.scrollIncrement * -1;
4583 case "scrollbutton-down":
4584 pixelsToScroll = tabStrip.scrollIncrement;
4588 tabStrip.scrollByPixels((ltr ? 1 : -1) * pixelsToScroll);
4591 if (effects == "move" &&
4592 this == event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0).parentNode) {
4593 ind.collapsed = true;
4594 this._animateTabMove(event);
4598 this._finishAnimateTabMove();
4600 if (effects == "link") {
4601 let tab = this._getDragTargetTab(event);
4603 if (!this._dragTime)
4604 this._dragTime = Date.now();
4605 if (Date.now() >= this._dragTime + this._dragOverDelay)
4606 this.selectedItem = tab;
4607 ind.collapsed = true;
4612 var rect = tabStrip.getBoundingClientRect();
4614 if (pixelsToScroll) {
4615 // if we are scrolling, put the drop indicator at the edge
4616 // so that it doesn't jump while scrolling
4617 let scrollRect = tabStrip.scrollClientRect;
4618 let minMargin = scrollRect.left - rect.left;
4619 let maxMargin = Math.min(minMargin + scrollRect.width,
4622 [minMargin, maxMargin] = [this.clientWidth - maxMargin,
4623 this.clientWidth - minMargin];
4624 newMargin = (pixelsToScroll > 0) ? maxMargin : minMargin;
4627 let newIndex = this._getDropIndex(event);
4628 if (newIndex == this.childNodes.length) {
4629 let tabRect = this.childNodes[newIndex-1].getBoundingClientRect();
4631 newMargin = tabRect.right - rect.left;
4633 newMargin = rect.right - tabRect.left;
4636 let tabRect = this.childNodes[newIndex].getBoundingClientRect();
4638 newMargin = tabRect.left - rect.left;
4640 newMargin = rect.right - tabRect.right;
4644 ind.collapsed = false;
4646 newMargin += ind.clientWidth / 2;
4650 ind.style.transform = "translate(" + Math.round(newMargin) + "px)";
4651 ind.style.MozMarginStart = (-ind.clientWidth) + "px";
4654 <handler event="drop"><![CDATA[
4655 var dt = event.dataTransfer;
4656 var dropEffect = dt.dropEffect;
4658 if (dropEffect != "link") { // copy or move
4659 draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
4660 // not our drop then
4665 this._tabDropIndicator.collapsed = true;
4666 event.stopPropagation();
4667 if (draggedTab && dropEffect == "copy") {
4668 // copy the dropped tab (wherever it's from)
4669 let newIndex = this._getDropIndex(event);
4670 let newTab = this.tabbrowser.duplicateTab(draggedTab);
4671 this.tabbrowser.moveTabTo(newTab, newIndex);
4672 if (draggedTab.parentNode != this || event.shiftKey)
4673 this.selectedItem = newTab;
4674 } else if (draggedTab && draggedTab.parentNode == this) {
4675 this._finishAnimateTabMove();
4677 // actually move the dragged tab
4678 if ("animDropIndex" in draggedTab._dragData) {
4679 let newIndex = draggedTab._dragData.animDropIndex;
4680 if (newIndex > draggedTab._tPos)
4682 this.tabbrowser.moveTabTo(draggedTab, newIndex);
4684 } else if (draggedTab) {
4685 // swap the dropped tab with a new one we create and then close
4686 // it in the other window (making it seem to have moved between
4688 let newIndex = this._getDropIndex(event);
4689 let newTab = this.tabbrowser.addTab("about:blank");
4690 let newBrowser = this.tabbrowser.getBrowserForTab(newTab);
4691 // Stop the about:blank load
4693 // make sure it has a docshell
4694 newBrowser.docShell;
4696 let numPinned = this.tabbrowser._numPinnedTabs;
4697 if (newIndex < numPinned || draggedTab.pinned && newIndex == numPinned)
4698 this.tabbrowser.pinTab(newTab);
4699 this.tabbrowser.moveTabTo(newTab, newIndex);
4701 // We need to select the tab before calling swapBrowsersAndCloseOther
4702 // so that window.content in chrome windows points to the right tab
4703 // when pagehide/show events are fired.
4704 this.tabbrowser.selectedTab = newTab;
4706 draggedTab.parentNode._finishAnimateTabMove();
4707 this.tabbrowser.swapBrowsersAndCloseOther(newTab, draggedTab);
4709 // Call updateCurrentBrowser to make sure the URL bar is up to date
4710 // for our new tab after we've done swapBrowsersAndCloseOther.
4711 this.tabbrowser.updateCurrentBrowser(true);
4713 // Pass true to disallow dropping javascript: or data: urls
4716 url = browserDragAndDrop.drop(event, { }, true);
4722 let bgLoad = Services.prefs.getBoolPref("browser.tabs.loadInBackground");
4727 let tab = this._getDragTargetTab(event);
4728 if (!tab || dropEffect == "copy") {
4729 // We're adding a new tab.
4730 let newIndex = this._getDropIndex(event);
4731 let newTab = this.tabbrowser.loadOneTab(url, {inBackground: bgLoad, allowThirdPartyFixup: true});
4732 this.tabbrowser.moveTabTo(newTab, newIndex);
4734 // Load in an existing tab.
4736 let webNav = Ci.nsIWebNavigation;
4737 let flags = webNav.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP |
4738 webNav.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
4739 this.tabbrowser.getBrowserForTab(tab).loadURIWithFlags(url, flags);
4741 this.selectedItem = tab;
4743 // Just ignore invalid urls
4749 delete draggedTab._dragData;
4753 <handler event="dragend"><![CDATA[
4754 // Note: while this case is correctly handled here, this event
4755 // isn't dispatched when the tab is moved within the tabstrip,
4758 this._finishAnimateTabMove();
4760 var dt = event.dataTransfer;
4761 var draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
4762 if (dt.mozUserCancelled || dt.dropEffect != "none" || this._isCustomizing) {
4763 delete draggedTab._dragData;
4767 // Disable detach within the browser toolbox
4768 var eX = event.screenX;
4769 var eY = event.screenY;
4770 var wX = window.screenX;
4771 // check if the drop point is horizontally within the window
4772 if (eX > wX && eX < (wX + window.outerWidth)) {
4773 let bo = this.mTabstrip.boxObject;
4774 // also avoid detaching if the the tab was dropped too close to
4775 // the tabbar (half a tab)
4776 let endScreenY = bo.screenY + 1.5 * bo.height;
4777 if (eY < endScreenY && eY > window.screenY)
4781 // screen.availLeft et. al. only check the screen that this window is on,
4782 // but we want to look at the screen the tab is being dropped onto.
4783 var sX = {}, sY = {}, sWidth = {}, sHeight = {};
4784 Cc["@mozilla.org/gfx/screenmanager;1"]
4785 .getService(Ci.nsIScreenManager)
4786 .screenForRect(eX, eY, 1, 1)
4787 .GetAvailRect(sX, sY, sWidth, sHeight);
4788 // ensure new window entirely within screen
4789 var winWidth = Math.min(window.outerWidth, sWidth.value);
4790 var winHeight = Math.min(window.outerHeight, sHeight.value);
4791 var left = Math.min(Math.max(eX - draggedTab._dragData.offsetX, sX.value),
4792 sX.value + sWidth.value - winWidth);
4793 var top = Math.min(Math.max(eY - draggedTab._dragData.offsetY, sY.value),
4794 sY.value + sHeight.value - winHeight);
4796 delete draggedTab._dragData;
4798 if (this.tabbrowser.tabs.length == 1) {
4799 // resize _before_ move to ensure the window fits the new screen. if
4800 // the window is too large for its screen, the window manager may do
4801 // automatic repositioning.
4802 window.resizeTo(winWidth, winHeight);
4803 window.moveTo(left, top);
4806 this.tabbrowser.replaceTabWithWindow(draggedTab, { screenX: left,
4809 outerWidth: winWidth,
4810 outerHeight: winHeight
4814 event.stopPropagation();
4817 <handler event="dragexit"><![CDATA[
4820 // This does not work at all (see bug 458613)
4821 var target = event.relatedTarget;
4822 while (target && target != this)
4823 target = target.parentNode;
4827 this._tabDropIndicator.collapsed = true;
4828 event.stopPropagation();
4833 <!-- close-tab-button binding
4834 This binding relies on the structure of the tabbrowser binding.
4835 Therefore it should only be used as a child of the tab or the tabs
4836 element (in both cases, when they are anonymous nodes of <tabbrowser>).
4838 <binding id="tabbrowser-close-tab-button"
4839 extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-image">
4841 <handler event="click" button="0"><![CDATA[
4842 var bindingParent = document.getBindingParent(this);
4843 var tabContainer = bindingParent.parentNode;
4844 tabContainer.tabbrowser.removeTab(bindingParent, {animate: true, byMouse: true});
4845 // This enables double-click protection for the tab container
4846 // (see tabbrowser-tabs 'click' handler).
4847 tabContainer._blockDblClick = true;
4850 <handler event="dblclick" button="0" phase="capturing">
4851 // for the one-close-button case
4852 event.stopPropagation();
4855 <handler event="dragstart">
4856 event.stopPropagation();
4861 <binding id="tabbrowser-tab" display="xul:hbox"
4862 extends="chrome://global/content/bindings/tabbox.xml#tab">
4864 <stylesheet src="chrome://browser/content/tabbrowser.css"/>
4867 <content context="tabContextMenu" closetabtext="&closeTab.label;">
4868 <xul:stack class="tab-stack" flex="1">
4869 <xul:hbox xbl:inherits="pinned,selected,titlechanged,fadein"
4870 class="tab-background">
4871 <xul:hbox xbl:inherits="pinned,selected,titlechanged"
4872 class="tab-background-start"/>
4873 <xul:hbox xbl:inherits="pinned,selected,titlechanged"
4874 class="tab-background-middle"/>
4875 <xul:hbox xbl:inherits="pinned,selected,titlechanged"
4876 class="tab-background-end"/>
4878 <xul:hbox xbl:inherits="pinned,selected,titlechanged"
4879 class="tab-content" align="center">
4880 <xul:image xbl:inherits="fadein,pinned,busy,progress,selected"
4881 class="tab-throbber"
4884 <xul:image xbl:inherits="src=image,fadein,pinned,selected"
4885 anonid="tab-icon-image"
4886 class="tab-icon-image"
4888 role="presentation"/>
4891 xbl:inherits="value=visibleLabel,crop,accesskey,fadein,pinned,selected"
4892 class="tab-text tab-label"
4893 role="presentation"/>
4894 <xul:toolbarbutton anonid="close-button"
4895 xbl:inherits="fadein,pinned,selected"
4896 class="tab-close-button close-icon"/>
4902 <property name="label">
4904 return this.getAttribute("label");
4907 this.setAttribute("label", val);
4908 let event = new CustomEvent("TabLabelModified", {
4912 this.dispatchEvent(event);
4914 // Let listeners prevent synchronizing the actual label to the
4915 // visible label (allowing them to override the visible label).
4916 if (!event.defaultPrevented)
4917 this.visibleLabel = val;
4920 <property name="visibleLabel">
4922 return this.getAttribute("visibleLabel");
4925 this.setAttribute("visibleLabel", val);
4928 <property name="pinned" readonly="true">
4930 return this.getAttribute("pinned") == "true";
4933 <property name="hidden" readonly="true">
4935 return this.getAttribute("hidden") == "true";
4939 <property name="lastAccessed">
4941 return this.selected ? Date.now() : this._lastAccessed;
4944 this._lastAccessed = val;
4947 <field name="_lastAccessed">0</field>
4949 <field name="mOverCloseButton">false</field>
4950 <field name="mCorrespondingMenuitem">null</field>
4951 <field name="closing">false</field>
4953 <method name="_mouseenter">
4955 if (this.hidden || this.closing)
4958 let tabContainer = this.parentNode;
4959 let visibleTabs = tabContainer.tabbrowser.visibleTabs;
4960 let tabIndex = visibleTabs.indexOf(this);
4961 if (tabIndex == 0) {
4962 tabContainer._beforeHoveredTab = null;
4964 let candidate = visibleTabs[tabIndex - 1];
4965 if (!candidate.selected) {
4966 tabContainer._beforeHoveredTab = candidate;
4967 candidate.setAttribute("beforehovered", "true");
4971 if (tabIndex == visibleTabs.length - 1) {
4972 tabContainer._afterHoveredTab = null;
4974 let candidate = visibleTabs[tabIndex + 1];
4975 if (!candidate.selected) {
4976 tabContainer._afterHoveredTab = candidate;
4977 candidate.setAttribute("afterhovered", "true");
4981 tabContainer._hoveredTab = this;
4985 <method name="_mouseleave">
4987 let tabContainer = this.parentNode;
4988 if (tabContainer._beforeHoveredTab) {
4989 tabContainer._beforeHoveredTab.removeAttribute("beforehovered");
4990 tabContainer._beforeHoveredTab = null;
4992 if (tabContainer._afterHoveredTab) {
4993 tabContainer._afterHoveredTab.removeAttribute("afterhovered");
4994 tabContainer._afterHoveredTab = null;
4997 tabContainer._hoveredTab = null;
5003 <handler event="mouseover"><![CDATA[
5004 let anonid = event.originalTarget.getAttribute("anonid");
5005 if (anonid == "close-button")
5006 this.mOverCloseButton = true;
5010 <handler event="mouseout"><![CDATA[
5011 let anonid = event.originalTarget.getAttribute("anonid");
5012 if (anonid == "close-button")
5013 this.mOverCloseButton = false;
5017 <handler event="dragstart" phase="capturing">
5018 this.style.MozUserFocus = '';
5020 <handler event="mousedown" phase="capturing">
5022 if (this.selected) {
5023 this.style.MozUserFocus = 'ignore';
5024 this.clientTop; // just using this to flush style updates
5025 } else if (this.mOverCloseButton) {
5026 // Prevent tabbox.xml from selecting the tab.
5027 event.stopPropagation();
5031 <handler event="mouseup">
5032 this.style.MozUserFocus = '';
5037 <binding id="tabbrowser-alltabs-popup"
5038 extends="chrome://global/content/bindings/popup.xml#popup">
5039 <implementation implements="nsIDOMEventListener">
5040 <method name="_tabOnAttrModified">
5041 <parameter name="aEvent"/>
5043 var tab = aEvent.target;
5044 if (tab.mCorrespondingMenuitem)
5045 this._setMenuitemAttributes(tab.mCorrespondingMenuitem, tab);
5049 <method name="_tabOnTabClose">
5050 <parameter name="aEvent"/>
5052 var tab = aEvent.target;
5053 if (tab.mCorrespondingMenuitem)
5054 this.removeChild(tab.mCorrespondingMenuitem);
5058 <method name="handleEvent">
5059 <parameter name="aEvent"/>
5061 switch (aEvent.type) {
5062 case "TabAttrModified":
5063 this._tabOnAttrModified(aEvent);
5066 this._tabOnTabClose(aEvent);
5069 this._updateTabsVisibilityStatus();
5075 <method name="_updateTabsVisibilityStatus">
5077 var tabContainer = gBrowser.tabContainer;
5078 // We don't want menu item decoration unless there is overflow.
5079 if (tabContainer.getAttribute("overflow") != "true")
5082 var tabstripBO = tabContainer.mTabstrip.scrollBoxObject;
5083 for (var i = 0; i < this.childNodes.length; i++) {
5084 let curTab = this.childNodes[i].tab;
5085 if (!curTab) // "Tab Groups" menuitem and its menuseparator
5087 let curTabBO = curTab.boxObject;
5088 if (curTabBO.screenX >= tabstripBO.screenX &&
5089 curTabBO.screenX + curTabBO.width <= tabstripBO.screenX + tabstripBO.width)
5090 this.childNodes[i].setAttribute("tabIsVisible", "true");
5092 this.childNodes[i].removeAttribute("tabIsVisible");
5097 <method name="_createTabMenuItem">
5098 <parameter name="aTab"/>
5100 var menuItem = document.createElementNS(
5101 "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
5104 menuItem.setAttribute("class", "menuitem-iconic alltabs-item menuitem-with-favicon");
5106 this._setMenuitemAttributes(menuItem, aTab);
5108 aTab.mCorrespondingMenuitem = menuItem;
5109 menuItem.tab = aTab;
5111 this.appendChild(menuItem);
5115 <method name="_setMenuitemAttributes">
5116 <parameter name="aMenuitem"/>
5117 <parameter name="aTab"/>
5119 aMenuitem.setAttribute("label", aTab.label);
5120 aMenuitem.setAttribute("crop", aTab.getAttribute("crop"));
5122 if (aTab.hasAttribute("busy")) {
5123 aMenuitem.setAttribute("busy", aTab.getAttribute("busy"));
5124 aMenuitem.removeAttribute("image");
5126 aMenuitem.setAttribute("image", aTab.getAttribute("image"));
5127 aMenuitem.removeAttribute("busy");
5130 if (aTab.hasAttribute("pending"))
5131 aMenuitem.setAttribute("pending", aTab.getAttribute("pending"));
5133 aMenuitem.removeAttribute("pending");
5136 aMenuitem.setAttribute("selected", "true");
5138 aMenuitem.removeAttribute("selected");
5144 <handler event="popupshowing">
5146 document.getElementById("alltabs_undoCloseTab").disabled =
5147 SessionStore.getClosedTabCount(window) == 0;
5149 var tabcontainer = gBrowser.tabContainer;
5151 // Listen for changes in the tab bar.
5152 tabcontainer.addEventListener("TabAttrModified", this, false);
5153 tabcontainer.addEventListener("TabClose", this, false);
5154 tabcontainer.mTabstrip.addEventListener("scroll", this, false);
5156 let tabs = gBrowser.visibleTabs;
5157 for (var i = 0; i < tabs.length; i++) {
5158 if (!tabs[i].pinned)
5159 this._createTabMenuItem(tabs[i]);
5161 this._updateTabsVisibilityStatus();
5164 <handler event="popuphidden">
5166 // clear out the menu popup and remove the listeners
5167 for (let i = this.childNodes.length - 1; i > 0; i--) {
5168 let menuItem = this.childNodes[i];
5170 menuItem.tab.mCorrespondingMenuitem = null;
5171 this.removeChild(menuItem);
5174 var tabcontainer = gBrowser.tabContainer;
5175 tabcontainer.mTabstrip.removeEventListener("scroll", this, false);
5176 tabcontainer.removeEventListener("TabAttrModified", this, false);
5177 tabcontainer.removeEventListener("TabClose", this, false);
5180 <handler event="DOMMenuItemActive">
5182 var tab = event.target.tab;
5184 let overLink = tab.linkedBrowser.currentURI.spec;
5185 if (overLink == "about:blank")
5187 XULBrowserWindow.setOverLink(overLink, null);
5191 <handler event="DOMMenuItemInactive">
5193 XULBrowserWindow.setOverLink("", null);
5196 <handler event="command"><![CDATA[
5197 if (event.target.tab)
5198 gBrowser.selectedTab = event.target.tab;
5204 <binding id="statuspanel" display="xul:hbox">
5206 <xul:hbox class="statuspanel-inner">
5207 <xul:label class="statuspanel-label"
5210 xbl:inherits="value=label,crop,mirror"
5216 <implementation implements="nsIDOMEventListener">
5217 <constructor><![CDATA[
5218 window.addEventListener("resize", this, false);
5221 <destructor><![CDATA[
5222 window.removeEventListener("resize", this, false);
5223 MousePosTracker.removeListener(this);
5226 <property name="label">
5229 this.removeAttribute("mirror");
5230 this.removeAttribute("sizelimit");
5233 this.style.minWidth = this.getAttribute("type") == "status" &&
5234 this.getAttribute("previoustype") == "status"
5235 ? getComputedStyle(this).width : "";
5238 this.setAttribute("label", val);
5239 this.removeAttribute("inactive");
5240 this._calcMouseTargetRect();
5241 MousePosTracker.addListener(this);
5243 this.setAttribute("inactive", "true");
5244 MousePosTracker.removeListener(this);
5250 return this.hasAttribute("inactive") ? "" : this.getAttribute("label");
5254 <method name="getMouseTargetRect">
5256 return this._mouseTargetRect;
5260 <method name="onMouseEnter">
5266 <method name="onMouseLeave">
5272 <method name="handleEvent">
5273 <parameter name="event"/>
5278 switch (event.type) {
5280 this._calcMouseTargetRect();
5286 <method name="_calcMouseTargetRect">
5288 let container = this.parentNode;
5289 let alignRight = (getComputedStyle(container).direction == "rtl");
5290 let panelRect = this.getBoundingClientRect();
5291 let containerRect = container.getBoundingClientRect();
5293 this._mouseTargetRect = {
5295 bottom: panelRect.bottom,
5296 left: alignRight ? containerRect.right - panelRect.width : containerRect.left,
5297 right: alignRight ? containerRect.right : containerRect.left + panelRect.width
5302 <method name="_mirror">
5304 if (this.hasAttribute("mirror"))
5305 this.removeAttribute("mirror");
5307 this.setAttribute("mirror", "true");
5309 if (!this.hasAttribute("sizelimit")) {
5310 this.setAttribute("sizelimit", "true");
5311 this._calcMouseTargetRect();
5318 <binding id="tabbrowser-tabpanels"
5319 extends="chrome://global/content/bindings/tabbox.xml#tabpanels">
5321 <field name="_selectedIndex">0</field>
5323 <property name="selectedIndex">
5326 return this._selectedIndex;
5332 if (val < 0 || val >= this.childNodes.length)
5335 let toTab = this.getRelatedElement(this.childNodes[val]);
5336 let fromTab = this._selectedPanel ? this.getRelatedElement(this._selectedPanel)
5339 let switchPromise = gBrowser._prepareForTabSwitch(toTab, fromTab);
5341 var panel = this._selectedPanel;
5342 this._selectedPanel = this.childNodes[val];
5343 if (this._selectedPanel != panel) {
5344 var event = document.createEvent("Events");
5345 event.initEvent("select", true, true);
5346 this.dispatchEvent(event);
5349 this._selectedIndex = val;
5351 switchPromise.then(() => {
5352 this.setAttribute("selectedIndex", val);
5353 gBrowser._finalizeTabSwitch(toTab, fromTab);
5355 // If the promise rejected, that means we don't want to actually
5356 // flip the deck, so we cancel the tab switch.
5357 gBrowser._cancelTabSwitch(toTab);