browser/base/content/tabbrowser.xml

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

mercurial