1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 ChromeUtils.defineModuleGetter(
10 "resource:///modules/PanelMultiView.jsm"
13 var EXPORTED_SYMBOLS = ["TabsPanel"];
15 function setAttributes(element, attrs) {
16 for (let [name, value] of Object.entries(attrs)) {
18 element.setAttribute(name, value);
20 element.removeAttribute(name);
26 constructor({ className, filterFn, insertBefore, containerNode }) {
27 this.className = className;
28 this.filterFn = filterFn;
29 this.insertBefore = insertBefore;
30 this.containerNode = containerNode;
32 this.doc = containerNode.ownerDocument;
33 this.gBrowser = this.doc.defaultView.gBrowser;
34 this.tabToElement = new Map();
35 this.listenersRegistered = false;
39 return this.tabToElement.values();
44 case "TabAttrModified":
45 this._tabAttrModified(event.target);
48 this._tabClose(event.target);
51 this._moveTab(event.target);
54 if (!this.filterFn(event.target)) {
55 this._tabClose(event.target);
59 this._selectTab(event.target.tab);
65 if (this.gBrowser.selectedTab != tab) {
66 this.gBrowser.selectedTab = tab;
68 this.gBrowser.tabContainer._handleTabSelect();
73 * Populate the popup with menuitems and setup the listeners.
76 let fragment = this.doc.createDocumentFragment();
78 for (let tab of this.gBrowser.tabs) {
79 if (this.filterFn(tab)) {
80 fragment.appendChild(this._createRow(tab));
84 this._addElement(fragment);
85 this._setupListeners();
88 _addElement(elementOrFragment) {
89 this.containerNode.insertBefore(elementOrFragment, this.insertBefore);
93 * Remove the menuitems from the DOM, cleanup internal state and listeners.
96 for (let item of this.rows) {
99 this.tabToElement = new Map();
100 this._cleanupListeners();
104 this.listenersRegistered = true;
105 this.gBrowser.tabContainer.addEventListener("TabAttrModified", this);
106 this.gBrowser.tabContainer.addEventListener("TabClose", this);
107 this.gBrowser.tabContainer.addEventListener("TabMove", this);
108 this.gBrowser.tabContainer.addEventListener("TabPinned", this);
111 _cleanupListeners() {
112 this.gBrowser.tabContainer.removeEventListener("TabAttrModified", this);
113 this.gBrowser.tabContainer.removeEventListener("TabClose", this);
114 this.gBrowser.tabContainer.removeEventListener("TabMove", this);
115 this.gBrowser.tabContainer.removeEventListener("TabPinned", this);
116 this.listenersRegistered = false;
119 _tabAttrModified(tab) {
120 let item = this.tabToElement.get(tab);
122 if (!this.filterFn(tab)) {
123 // The tab no longer matches our criteria, remove it.
124 this._removeItem(item, tab);
126 this._setRowAttributes(item, tab);
128 } else if (this.filterFn(tab)) {
129 // The tab now matches our criteria, add a row for it.
135 let item = this.tabToElement.get(tab);
137 this._removeItem(item, tab);
142 if (!this.filterFn(newTab)) {
145 let newRow = this._createRow(newTab);
146 let nextTab = newTab.nextElementSibling;
148 while (nextTab && !this.filterFn(nextTab)) {
149 nextTab = nextTab.nextElementSibling;
152 // If we found a tab after this one in the list, insert the new row before it.
153 let nextRow = this.tabToElement.get(nextTab);
155 nextRow.parentNode.insertBefore(newRow, nextRow);
157 // If there's no next tab then insert it as usual.
158 this._addElement(newRow);
162 let item = this.tabToElement.get(tab);
164 this._removeItem(item, tab);
168 _removeItem(item, tab) {
169 this.tabToElement.delete(tab);
174 const TABS_PANEL_EVENTS = {
176 hide: "PanelMultiViewHidden",
179 class TabsPanel extends TabsListBase {
183 containerNode: opts.containerNode || opts.view.firstElementChild,
185 this.view = opts.view;
186 this.view.addEventListener(TABS_PANEL_EVENTS.show, this);
187 this.panelMultiView = null;
191 switch (event.type) {
192 case TABS_PANEL_EVENTS.hide:
193 if (event.target == this.panelMultiView) {
195 this.panelMultiView = null;
198 case TABS_PANEL_EVENTS.show:
199 if (!this.listenersRegistered && event.target == this.view) {
200 this.panelMultiView = this.view.panelMultiView;
201 this._populate(event);
205 if (event.target.hasAttribute("toggle-mute")) {
206 event.target.tab.toggleMuteAudio();
211 super.handleEvent(event);
217 super._populate(event);
219 // The loading throbber can't be set until the toolbarbutton is rendered,
220 // so set the image attributes again now that the elements are in the DOM.
221 for (let row of this.rows) {
222 this._setImageAttributes(row, row.tab);
227 super._selectTab(tab);
228 PanelMultiView.hidePopup(this.view.closest("panel"));
232 super._setupListeners();
233 this.panelMultiView.addEventListener(TABS_PANEL_EVENTS.hide, this);
236 _cleanupListeners() {
237 super._cleanupListeners();
238 this.panelMultiView.removeEventListener(TABS_PANEL_EVENTS.hide, this);
243 let row = doc.createXULElement("toolbaritem");
244 row.setAttribute("class", "all-tabs-item");
245 row.setAttribute("context", "tabContextMenu");
246 if (this.className) {
247 row.classList.add(this.className);
250 row.addEventListener("command", this);
251 this.tabToElement.set(tab, row);
253 let button = doc.createXULElement("toolbarbutton");
256 "all-tabs-button subviewbutton subviewbutton-iconic"
258 button.setAttribute("flex", "1");
259 button.setAttribute("crop", "right");
262 row.appendChild(button);
264 let secondaryButton = doc.createXULElement("toolbarbutton");
265 secondaryButton.setAttribute(
267 "all-tabs-secondary-button subviewbutton subviewbutton-iconic"
269 secondaryButton.setAttribute("closemenu", "none");
270 secondaryButton.setAttribute("toggle-mute", "true");
271 secondaryButton.tab = tab;
272 row.appendChild(secondaryButton);
274 this._setRowAttributes(row, tab);
279 _setRowAttributes(row, tab) {
280 setAttributes(row, { selected: tab.selected });
282 let busy = tab.getAttribute("busy");
283 let button = row.firstElementChild;
284 setAttributes(button, {
287 image: !busy && tab.getAttribute("image"),
288 iconloadingprincipal: tab.getAttribute("iconloadingprincipal"),
291 this._setImageAttributes(row, tab);
293 let secondaryButton = row.querySelector(".all-tabs-secondary-button");
294 setAttributes(secondaryButton, {
296 soundplaying: tab.soundPlaying,
297 pictureinpicture: tab.pictureinpicture,
298 hidden: !(tab.muted || tab.soundPlaying),
302 _setImageAttributes(row, tab) {
303 let button = row.firstElementChild;
304 let image = button.icon;
307 let busy = tab.getAttribute("busy");
308 let progress = tab.getAttribute("progress");
309 setAttributes(image, { busy, progress });
311 image.classList.add("tab-throbber-tabslist");
313 image.classList.remove("tab-throbber-tabslist");