forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			1048 lines
		
	
	
	
		
			32 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1048 lines
		
	
	
	
		
			32 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /*
 | |
| #ifdef 0
 | |
|  * This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 | |
| #endif
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * Tab previews utility, produces thumbnails
 | |
|  */
 | |
| var tabPreviews = {
 | |
|   aspectRatio: 0.5625, // 16:9
 | |
| 
 | |
|   get width() {
 | |
|     delete this.width;
 | |
|     return this.width = Math.ceil(screen.availWidth / 5.75);
 | |
|   },
 | |
| 
 | |
|   get height() {
 | |
|     delete this.height;
 | |
|     return this.height = Math.round(this.width * this.aspectRatio);
 | |
|   },
 | |
| 
 | |
|   init: function tabPreviews_init() {
 | |
|     if (this._selectedTab)
 | |
|       return;
 | |
|     this._selectedTab = gBrowser.selectedTab;
 | |
| 
 | |
|     gBrowser.tabContainer.addEventListener("TabSelect", this, false);
 | |
|     gBrowser.tabContainer.addEventListener("SSTabRestored", this, false);
 | |
|   },
 | |
| 
 | |
|   get: function tabPreviews_get(aTab) {
 | |
|     let uri = aTab.linkedBrowser.currentURI.spec;
 | |
| 
 | |
|     if (aTab.__thumbnail_lastURI &&
 | |
|         aTab.__thumbnail_lastURI != uri) {
 | |
|       aTab.__thumbnail = null;
 | |
|       aTab.__thumbnail_lastURI = null;
 | |
|     }
 | |
| 
 | |
|     if (aTab.__thumbnail)
 | |
|       return aTab.__thumbnail;
 | |
| 
 | |
|     if (aTab.getAttribute("pending") == "true") {
 | |
|       let img = new Image;
 | |
|       img.src = PageThumbs.getThumbnailURL(uri);
 | |
|       return img;
 | |
|     }
 | |
| 
 | |
|     return this.capture(aTab, !aTab.hasAttribute("busy"));
 | |
|   },
 | |
| 
 | |
|   capture: function tabPreviews_capture(aTab, aStore) {
 | |
|     var thumbnail = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
 | |
|     thumbnail.mozOpaque = true;
 | |
|     thumbnail.height = this.height;
 | |
|     thumbnail.width = this.width;
 | |
| 
 | |
|     var ctx = thumbnail.getContext("2d");
 | |
|     var win = aTab.linkedBrowser.contentWindow;
 | |
|     var snippetWidth = win.innerWidth * .6;
 | |
|     var scale = this.width / snippetWidth;
 | |
|     ctx.scale(scale, scale);
 | |
|     ctx.drawWindow(win, win.scrollX, win.scrollY,
 | |
|                    snippetWidth, snippetWidth * this.aspectRatio, "rgb(255,255,255)");
 | |
| 
 | |
|     if (aStore) {
 | |
|       aTab.__thumbnail = thumbnail;
 | |
|       aTab.__thumbnail_lastURI = aTab.linkedBrowser.currentURI.spec;
 | |
|     }
 | |
|     return thumbnail;
 | |
|   },
 | |
| 
 | |
|   handleEvent: function tabPreviews_handleEvent(event) {
 | |
|     switch (event.type) {
 | |
|       case "TabSelect":
 | |
|         if (this._selectedTab &&
 | |
|             this._selectedTab.parentNode &&
 | |
|             !this._pendingUpdate) {
 | |
|           // Generate a thumbnail for the tab that was selected.
 | |
|           // The timeout keeps the UI snappy and prevents us from generating thumbnails
 | |
|           // for tabs that will be closed. During that timeout, don't generate other
 | |
|           // thumbnails in case multiple TabSelect events occur fast in succession.
 | |
|           this._pendingUpdate = true;
 | |
|           setTimeout(function (self, aTab) {
 | |
|             self._pendingUpdate = false;
 | |
|             if (aTab.parentNode && !aTab.hasAttribute("busy"))
 | |
|               self.capture(aTab, true);
 | |
|           }, 2000, this, this._selectedTab);
 | |
|         }
 | |
|         this._selectedTab = event.target;
 | |
|         break;
 | |
|       case "SSTabRestored":
 | |
|         this.capture(event.target, true);
 | |
|         break;
 | |
|     }
 | |
|   }
 | |
| };
 | |
| 
 | |
| var tabPreviewPanelHelper = {
 | |
|   opening: function (host) {
 | |
|     host.panel.hidden = false;
 | |
| 
 | |
|     var handler = this._generateHandler(host);
 | |
|     host.panel.addEventListener("popupshown", handler, false);
 | |
|     host.panel.addEventListener("popuphiding", handler, false);
 | |
| 
 | |
|     host._prevFocus = document.commandDispatcher.focusedElement;
 | |
|   },
 | |
|   _generateHandler: function (host) {
 | |
|     var self = this;
 | |
|     return function (event) {
 | |
|       if (event.target == host.panel) {
 | |
|         host.panel.removeEventListener(event.type, arguments.callee, false);
 | |
|         self["_" + event.type](host);
 | |
|       }
 | |
|     };
 | |
|   },
 | |
|   _popupshown: function (host) {
 | |
|     if ("setupGUI" in host)
 | |
|       host.setupGUI();
 | |
|   },
 | |
|   _popuphiding: function (host) {
 | |
|     if ("suspendGUI" in host)
 | |
|       host.suspendGUI();
 | |
| 
 | |
|     if (host._prevFocus) {
 | |
|       Cc["@mozilla.org/focus-manager;1"]
 | |
|         .getService(Ci.nsIFocusManager)
 | |
|         .setFocus(host._prevFocus, Ci.nsIFocusManager.FLAG_NOSCROLL);
 | |
|       host._prevFocus = null;
 | |
|     } else
 | |
|       gBrowser.selectedBrowser.focus();
 | |
| 
 | |
|     if (host.tabToSelect) {
 | |
|       gBrowser.selectedTab = host.tabToSelect;
 | |
|       host.tabToSelect = null;
 | |
|     }
 | |
|   }
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Ctrl-Tab panel
 | |
|  */
 | |
| var ctrlTab = {
 | |
|   get panel () {
 | |
|     delete this.panel;
 | |
|     return this.panel = document.getElementById("ctrlTab-panel");
 | |
|   },
 | |
|   get showAllButton () {
 | |
|     delete this.showAllButton;
 | |
|     return this.showAllButton = document.getElementById("ctrlTab-showAll");
 | |
|   },
 | |
|   get previews () {
 | |
|     delete this.previews;
 | |
|     return this.previews = this.panel.getElementsByClassName("ctrlTab-preview");
 | |
|   },
 | |
|   get recentlyUsedLimit () {
 | |
|     delete this.recentlyUsedLimit;
 | |
|     return this.recentlyUsedLimit = gPrefService.getIntPref("browser.ctrlTab.recentlyUsedLimit");
 | |
|   },
 | |
|   get keys () {
 | |
|     var keys = {};
 | |
|     ["close", "find", "selectAll"].forEach(function (key) {
 | |
|       keys[key] = document.getElementById("key_" + key)
 | |
|                           .getAttribute("key")
 | |
|                           .toLocaleLowerCase().charCodeAt(0);
 | |
|     });
 | |
|     delete this.keys;
 | |
|     return this.keys = keys;
 | |
|   },
 | |
|   _selectedIndex: 0,
 | |
|   get selected () this._selectedIndex < 0 ?
 | |
|                     document.activeElement :
 | |
|                     this.previews.item(this._selectedIndex),
 | |
|   get isOpen   () this.panel.state == "open" || this.panel.state == "showing" || this._timer,
 | |
|   get tabCount () this.tabList.length,
 | |
|   get tabPreviewCount () Math.min(this.previews.length - 1, this.tabCount),
 | |
|   get canvasWidth () Math.min(tabPreviews.width,
 | |
|                               Math.ceil(screen.availWidth * .85 / this.tabPreviewCount)),
 | |
|   get canvasHeight () Math.round(this.canvasWidth * tabPreviews.aspectRatio),
 | |
| 
 | |
|   get tabList () {
 | |
|     if (this._tabList)
 | |
|       return this._tabList;
 | |
| 
 | |
|     // Using gBrowser.tabs instead of gBrowser.visibleTabs, as the latter
 | |
|     // exlcudes closing tabs, breaking the following loop in case the the
 | |
|     // selected tab is closing.
 | |
|     let list = Array.filter(gBrowser.tabs, function (tab) !tab.hidden);
 | |
| 
 | |
|     // Rotate the list until the selected tab is first
 | |
|     while (!list[0].selected)
 | |
|       list.push(list.shift());
 | |
| 
 | |
|     list = list.filter(function (tab) !tab.closing);
 | |
| 
 | |
|     if (this.recentlyUsedLimit != 0) {
 | |
|       let recentlyUsedTabs = [];
 | |
|       for (let tab of this._recentlyUsedTabs) {
 | |
|         if (!tab.hidden && !tab.closing) {
 | |
|           recentlyUsedTabs.push(tab);
 | |
|           if (this.recentlyUsedLimit > 0 && recentlyUsedTabs.length >= this.recentlyUsedLimit)
 | |
|             break;
 | |
|         }
 | |
|       }
 | |
|       for (let i = recentlyUsedTabs.length - 1; i >= 0; i--) {
 | |
|         list.splice(list.indexOf(recentlyUsedTabs[i]), 1);
 | |
|         list.unshift(recentlyUsedTabs[i]);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     return this._tabList = list;
 | |
|   },
 | |
| 
 | |
|   init: function ctrlTab_init() {
 | |
|     if (!this._recentlyUsedTabs) {
 | |
|       tabPreviews.init();
 | |
| 
 | |
|       this._recentlyUsedTabs = [gBrowser.selectedTab];
 | |
|       this._init(true);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   uninit: function ctrlTab_uninit() {
 | |
|     this._recentlyUsedTabs = null;
 | |
|     this._init(false);
 | |
|   },
 | |
| 
 | |
|   prefName: "browser.ctrlTab.previews",
 | |
|   readPref: function ctrlTab_readPref() {
 | |
|     var enable =
 | |
|       gPrefService.getBoolPref(this.prefName) &&
 | |
|       (!gPrefService.prefHasUserValue("browser.ctrlTab.disallowForScreenReaders") ||
 | |
|        !gPrefService.getBoolPref("browser.ctrlTab.disallowForScreenReaders"));
 | |
| 
 | |
|     if (enable)
 | |
|       this.init();
 | |
|     else
 | |
|       this.uninit();
 | |
|   },
 | |
|   observe: function (aSubject, aTopic, aPrefName) {
 | |
|     this.readPref();
 | |
|   },
 | |
| 
 | |
|   updatePreviews: function ctrlTab_updatePreviews() {
 | |
|     for (let i = 0; i < this.previews.length; i++)
 | |
|       this.updatePreview(this.previews[i], this.tabList[i]);
 | |
| 
 | |
|     var showAllLabel = gNavigatorBundle.getString("ctrlTab.showAll.label");
 | |
|     this.showAllButton.label =
 | |
|       PluralForm.get(this.tabCount, showAllLabel).replace("#1", this.tabCount);
 | |
|   },
 | |
| 
 | |
|   updatePreview: function ctrlTab_updatePreview(aPreview, aTab) {
 | |
|     if (aPreview == this.showAllButton)
 | |
|       return;
 | |
| 
 | |
|     aPreview._tab = aTab;
 | |
| 
 | |
|     if (aPreview.firstChild)
 | |
|       aPreview.removeChild(aPreview.firstChild);
 | |
|     if (aTab) {
 | |
|       let canvasWidth = this.canvasWidth;
 | |
|       let canvasHeight = this.canvasHeight;
 | |
|       aPreview.appendChild(tabPreviews.get(aTab));
 | |
|       aPreview.setAttribute("label", aTab.label);
 | |
|       aPreview.setAttribute("tooltiptext", aTab.label);
 | |
|       aPreview.setAttribute("crop", aTab.crop);
 | |
|       aPreview.setAttribute("canvaswidth", canvasWidth);
 | |
|       aPreview.setAttribute("canvasstyle",
 | |
|                             "max-width:" + canvasWidth + "px;" +
 | |
|                             "min-width:" + canvasWidth + "px;" +
 | |
|                             "max-height:" + canvasHeight + "px;" +
 | |
|                             "min-height:" + canvasHeight + "px;");
 | |
|       if (aTab.image)
 | |
|         aPreview.setAttribute("image", aTab.image);
 | |
|       else
 | |
|         aPreview.removeAttribute("image");
 | |
|       aPreview.hidden = false;
 | |
|     } else {
 | |
|       aPreview.hidden = true;
 | |
|       aPreview.removeAttribute("label");
 | |
|       aPreview.removeAttribute("tooltiptext");
 | |
|       aPreview.removeAttribute("image");
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   advanceFocus: function ctrlTab_advanceFocus(aForward) {
 | |
|     if (this._selectedIndex == -1) {
 | |
|       // No virtual selectedIndex, focus must be in the panel already.
 | |
|       if (aForward)
 | |
|         document.commandDispatcher.advanceFocus();
 | |
|       else
 | |
|         document.commandDispatcher.rewindFocus();
 | |
|     } else {
 | |
|       // Focus isn't in the panel yet, so we maintain a virtual selectedIndex.
 | |
|       do {
 | |
|         this._selectedIndex += aForward ? 1 : -1;
 | |
|         if (this._selectedIndex < 0)
 | |
|           this._selectedIndex = this.previews.length - 1;
 | |
|         else if (this._selectedIndex >= this.previews.length)
 | |
|           this._selectedIndex = 0;
 | |
|       } while (this.selected.hidden);
 | |
|     }
 | |
| 
 | |
|     if (this._timer) {
 | |
|       clearTimeout(this._timer);
 | |
|       this._timer = null;
 | |
|       this._openPanel();
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _mouseOverFocus: function ctrlTab_mouseOverFocus(aPreview) {
 | |
|     if (this._trackMouseOver)
 | |
|       aPreview.focus();
 | |
|   },
 | |
| 
 | |
|   pick: function ctrlTab_pick(aPreview) {
 | |
|     if (!this.tabCount)
 | |
|       return;
 | |
| 
 | |
|     var select = (aPreview || this.selected);
 | |
| 
 | |
|     if (select == this.showAllButton)
 | |
|       this.showAllTabs();
 | |
|     else
 | |
|       this.close(select._tab);
 | |
|   },
 | |
| 
 | |
|   showAllTabs: function ctrlTab_showAllTabs(aPreview) {
 | |
|     this.close();
 | |
|     document.getElementById("Browser:ShowAllTabs").doCommand();
 | |
|   },
 | |
| 
 | |
|   remove: function ctrlTab_remove(aPreview) {
 | |
|     if (aPreview._tab)
 | |
|       gBrowser.removeTab(aPreview._tab);
 | |
|   },
 | |
| 
 | |
|   attachTab: function ctrlTab_attachTab(aTab, aPos) {
 | |
|     if (aPos == 0)
 | |
|       this._recentlyUsedTabs.unshift(aTab);
 | |
|     else if (aPos)
 | |
|       this._recentlyUsedTabs.splice(aPos, 0, aTab);
 | |
|     else
 | |
|       this._recentlyUsedTabs.push(aTab);
 | |
|   },
 | |
|   detachTab: function ctrlTab_detachTab(aTab) {
 | |
|     var i = this._recentlyUsedTabs.indexOf(aTab);
 | |
|     if (i >= 0)
 | |
|       this._recentlyUsedTabs.splice(i, 1);
 | |
|   },
 | |
| 
 | |
|   open: function ctrlTab_open() {
 | |
|     if (this.isOpen)
 | |
|       return;
 | |
| 
 | |
|     allTabs.close();
 | |
| 
 | |
|     document.addEventListener("keyup", this, true);
 | |
| 
 | |
|     this.updatePreviews();
 | |
|     this._selectedIndex = 1;
 | |
| 
 | |
|     // Add a slight delay before showing the UI, so that a quick
 | |
|     // "ctrl-tab" keypress just flips back to the MRU tab.
 | |
|     this._timer = setTimeout(function (self) {
 | |
|       self._timer = null;
 | |
|       self._openPanel();
 | |
|     }, 200, this);
 | |
|   },
 | |
| 
 | |
|   _openPanel: function ctrlTab_openPanel() {
 | |
|     tabPreviewPanelHelper.opening(this);
 | |
| 
 | |
|     this.panel.width = Math.min(screen.availWidth * .99,
 | |
|                                 this.canvasWidth * 1.25 * this.tabPreviewCount);
 | |
|     var estimateHeight = this.canvasHeight * 1.25 + 75;
 | |
|     this.panel.openPopupAtScreen(screen.availLeft + (screen.availWidth - this.panel.width) / 2,
 | |
|                                  screen.availTop + (screen.availHeight - estimateHeight) / 2,
 | |
|                                  false);
 | |
|   },
 | |
| 
 | |
|   close: function ctrlTab_close(aTabToSelect) {
 | |
|     if (!this.isOpen)
 | |
|       return;
 | |
| 
 | |
|     if (this._timer) {
 | |
|       clearTimeout(this._timer);
 | |
|       this._timer = null;
 | |
|       this.suspendGUI();
 | |
|       if (aTabToSelect)
 | |
|         gBrowser.selectedTab = aTabToSelect;
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     this.tabToSelect = aTabToSelect;
 | |
|     this.panel.hidePopup();
 | |
|   },
 | |
| 
 | |
|   setupGUI: function ctrlTab_setupGUI() {
 | |
|     this.selected.focus();
 | |
|     this._selectedIndex = -1;
 | |
| 
 | |
|     // Track mouse movement after a brief delay so that the item that happens
 | |
|     // to be under the mouse pointer initially won't be selected unintentionally.
 | |
|     this._trackMouseOver = false;
 | |
|     setTimeout(function (self) {
 | |
|       if (self.isOpen)
 | |
|         self._trackMouseOver = true;
 | |
|     }, 0, this);
 | |
|   },
 | |
| 
 | |
|   suspendGUI: function ctrlTab_suspendGUI() {
 | |
|     document.removeEventListener("keyup", this, true);
 | |
| 
 | |
|     Array.forEach(this.previews, function (preview) {
 | |
|       this.updatePreview(preview, null);
 | |
|     }, this);
 | |
| 
 | |
|     this._tabList = null;
 | |
|   },
 | |
| 
 | |
|   onKeyPress: function ctrlTab_onKeyPress(event) {
 | |
|     var isOpen = this.isOpen;
 | |
| 
 | |
|     if (isOpen) {
 | |
|       event.preventDefault();
 | |
|       event.stopPropagation();
 | |
|     }
 | |
| 
 | |
|     switch (event.keyCode) {
 | |
|       case event.DOM_VK_TAB:
 | |
|         if (event.ctrlKey && !event.altKey && !event.metaKey) {
 | |
|           if (isOpen) {
 | |
|             this.advanceFocus(!event.shiftKey);
 | |
|           } else if (!event.shiftKey) {
 | |
|             event.preventDefault();
 | |
|             event.stopPropagation();
 | |
|             let tabs = gBrowser.visibleTabs;
 | |
|             if (tabs.length > 2) {
 | |
|               this.open();
 | |
|             } else if (tabs.length == 2) {
 | |
|               let index = gBrowser.selectedTab == tabs[0] ? 1 : 0;
 | |
|               gBrowser.selectedTab = tabs[index];
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|         break;
 | |
|       default:
 | |
|         if (isOpen && event.ctrlKey) {
 | |
|           if (event.keyCode == event.DOM_VK_DELETE) {
 | |
|             this.remove(this.selected);
 | |
|             break;
 | |
|           }
 | |
|           switch (event.charCode) {
 | |
|             case this.keys.close:
 | |
|               this.remove(this.selected);
 | |
|               break;
 | |
|             case this.keys.find:
 | |
|             case this.keys.selectAll:
 | |
|               this.showAllTabs();
 | |
|               break;
 | |
|           }
 | |
|         }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   removeClosingTabFromUI: function ctrlTab_removeClosingTabFromUI(aTab) {
 | |
|     if (this.tabCount == 2) {
 | |
|       this.close();
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     this._tabList = null;
 | |
|     this.updatePreviews();
 | |
| 
 | |
|     if (this.selected.hidden)
 | |
|       this.advanceFocus(false);
 | |
|     if (this.selected == this.showAllButton)
 | |
|       this.advanceFocus(false);
 | |
| 
 | |
|     // If the current tab is removed, another tab can steal our focus.
 | |
|     if (aTab == gBrowser.selectedTab && this.panel.state == "open") {
 | |
|       setTimeout(function (selected) {
 | |
|         selected.focus();
 | |
|       }, 0, this.selected);
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   handleEvent: function ctrlTab_handleEvent(event) {
 | |
|     switch (event.type) {
 | |
|       case "TabAttrModified":
 | |
|         // tab attribute modified (e.g. label, crop, busy, image, selected)
 | |
|         for (let i = this.previews.length - 1; i >= 0; i--) {
 | |
|           if (this.previews[i]._tab && this.previews[i]._tab == event.target) {
 | |
|             this.updatePreview(this.previews[i], event.target);
 | |
|             break;
 | |
|           }
 | |
|         }
 | |
|         break;
 | |
|       case "TabSelect":
 | |
|         this.detachTab(event.target);
 | |
|         this.attachTab(event.target, 0);
 | |
|         break;
 | |
|       case "TabOpen":
 | |
|         this.attachTab(event.target, 1);
 | |
|         break;
 | |
|       case "TabClose":
 | |
|         this.detachTab(event.target);
 | |
|         if (this.isOpen)
 | |
|           this.removeClosingTabFromUI(event.target);
 | |
|         break;
 | |
|       case "keypress":
 | |
|         this.onKeyPress(event);
 | |
|         break;
 | |
|       case "keyup":
 | |
|         if (event.keyCode == event.DOM_VK_CONTROL)
 | |
|           this.pick();
 | |
|         break;
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _init: function ctrlTab__init(enable) {
 | |
|     var toggleEventListener = enable ? "addEventListener" : "removeEventListener";
 | |
| 
 | |
|     var tabContainer = gBrowser.tabContainer;
 | |
|     tabContainer[toggleEventListener]("TabOpen", this, false);
 | |
|     tabContainer[toggleEventListener]("TabAttrModified", this, false);
 | |
|     tabContainer[toggleEventListener]("TabSelect", this, false);
 | |
|     tabContainer[toggleEventListener]("TabClose", this, false);
 | |
| 
 | |
|     document[toggleEventListener]("keypress", this, false);
 | |
|     gBrowser.mTabBox.handleCtrlTab = !enable;
 | |
| 
 | |
|     // If we're not running, hide the "Show All Tabs" menu item,
 | |
|     // as Shift+Ctrl+Tab will be handled by the tab bar.
 | |
|     document.getElementById("menu_showAllTabs").hidden = !enable;
 | |
| 
 | |
|     // Also disable the <key> to ensure Shift+Ctrl+Tab never triggers
 | |
|     // Show All Tabs.
 | |
|     var key_showAllTabs = document.getElementById("key_showAllTabs");
 | |
|     if (enable)
 | |
|       key_showAllTabs.removeAttribute("disabled");
 | |
|     else
 | |
|       key_showAllTabs.setAttribute("disabled", "true");
 | |
|   }
 | |
| };
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * All Tabs panel
 | |
|  */
 | |
| var allTabs = {
 | |
|   get panel () {
 | |
|     delete this.panel;
 | |
|     return this.panel = document.getElementById("allTabs-panel");
 | |
|   },
 | |
|   get filterField () {
 | |
|     delete this.filterField;
 | |
|     return this.filterField = document.getElementById("allTabs-filter");
 | |
|   },
 | |
|   get container () {
 | |
|     delete this.container;
 | |
|     return this.container = document.getElementById("allTabs-container");
 | |
|   },
 | |
|   get tabCloseButton () {
 | |
|     delete this.tabCloseButton;
 | |
|     return this.tabCloseButton = document.getElementById("allTabs-tab-close-button");
 | |
|   },
 | |
|   get toolbarButton() document.getElementById("alltabs-button"),
 | |
|   get previews () this.container.getElementsByClassName("allTabs-preview"),
 | |
|   get isOpen () this.panel.state == "open" || this.panel.state == "showing",
 | |
| 
 | |
|   init: function allTabs_init() {
 | |
|     if (this._initiated)
 | |
|       return;
 | |
|     this._initiated = true;
 | |
| 
 | |
|     tabPreviews.init();
 | |
| 
 | |
|     Array.forEach(gBrowser.tabs, function (tab) {
 | |
|       this._addPreview(tab);
 | |
|     }, this);
 | |
| 
 | |
|     gBrowser.tabContainer.addEventListener("TabOpen", this, false);
 | |
|     gBrowser.tabContainer.addEventListener("TabAttrModified", this, false);
 | |
|     gBrowser.tabContainer.addEventListener("TabMove", this, false);
 | |
|     gBrowser.tabContainer.addEventListener("TabClose", this, false);
 | |
|   },
 | |
| 
 | |
|   uninit: function allTabs_uninit() {
 | |
|     if (!this._initiated)
 | |
|       return;
 | |
| 
 | |
|     gBrowser.tabContainer.removeEventListener("TabOpen", this, false);
 | |
|     gBrowser.tabContainer.removeEventListener("TabAttrModified", this, false);
 | |
|     gBrowser.tabContainer.removeEventListener("TabMove", this, false);
 | |
|     gBrowser.tabContainer.removeEventListener("TabClose", this, false);
 | |
| 
 | |
|     while (this.container.hasChildNodes())
 | |
|       this.container.removeChild(this.container.firstChild);
 | |
| 
 | |
|     this._initiated = false;
 | |
|   },
 | |
| 
 | |
|   prefName: "browser.allTabs.previews",
 | |
|   readPref: function allTabs_readPref() {
 | |
|     var allTabsButton = this.toolbarButton;
 | |
|     if (!allTabsButton)
 | |
|       return;
 | |
| 
 | |
|     if (gPrefService.getBoolPref(this.prefName)) {
 | |
|       allTabsButton.removeAttribute("type");
 | |
|       allTabsButton.setAttribute("command", "Browser:ShowAllTabs");
 | |
|     } else {
 | |
|       allTabsButton.setAttribute("type", "menu");
 | |
|       allTabsButton.removeAttribute("command");
 | |
|       allTabsButton.removeAttribute("oncommand");
 | |
|     }
 | |
|   },
 | |
|   observe: function (aSubject, aTopic, aPrefName) {
 | |
|     this.readPref();
 | |
|   },
 | |
| 
 | |
|   pick: function allTabs_pick(aPreview) {
 | |
|     if (!aPreview)
 | |
|       aPreview = this._firstVisiblePreview;
 | |
|     if (aPreview)
 | |
|       this.tabToSelect = aPreview._tab;
 | |
| 
 | |
|     this.close();
 | |
|   },
 | |
| 
 | |
|   closeTab: function allTabs_closeTab(event) {
 | |
|     this.filterField.focus();
 | |
|     gBrowser.removeTab(event.currentTarget._targetPreview._tab);
 | |
|   },
 | |
| 
 | |
|   filter: function allTabs_filter() {
 | |
|     if (this._currentFilter == this.filterField.value)
 | |
|       return;
 | |
| 
 | |
|     this._currentFilter = this.filterField.value;
 | |
| 
 | |
|     var filter = this._currentFilter.split(/\s+/g);
 | |
|     this._visible = 0;
 | |
|     Array.forEach(this.previews, function (preview) {
 | |
|       var tab = preview._tab;
 | |
|       var matches = 0;
 | |
|       if (filter.length && !tab.hidden) {
 | |
|         let tabstring = tab.linkedBrowser.currentURI.spec;
 | |
|         try {
 | |
|           tabstring = decodeURI(tabstring);
 | |
|         } catch (e) {}
 | |
|         tabstring = tab.label + " " + tab.label.toLocaleLowerCase() + " " + tabstring;
 | |
|         for (let i = 0; i < filter.length; i++)
 | |
|           matches += tabstring.contains(filter[i]);
 | |
|       }
 | |
|       if (matches < filter.length || tab.hidden) {
 | |
|         preview.hidden = true;
 | |
|       }
 | |
|       else {
 | |
|         this._visible++;
 | |
|         this._updatePreview(preview);
 | |
|         preview.hidden = false;
 | |
|       }
 | |
|     }, this);
 | |
| 
 | |
|     this._reflow();
 | |
|   },
 | |
| 
 | |
|   open: function allTabs_open() {
 | |
|     var allTabsButton = this.toolbarButton;
 | |
|     if (allTabsButton &&
 | |
|         allTabsButton.getAttribute("type") == "menu") {
 | |
|       // Without setTimeout, the menupopup won't stay open when invoking
 | |
|       // "View > Show All Tabs" and the menu bar auto-hides.
 | |
|       setTimeout(function () {
 | |
|         allTabsButton.open = true;
 | |
|       }, 0);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     this.init();
 | |
| 
 | |
|     if (this.isOpen)
 | |
|       return;
 | |
| 
 | |
|     this._maxPanelHeight = Math.max(gBrowser.clientHeight, screen.availHeight / 2);
 | |
|     this._maxPanelWidth = Math.max(gBrowser.clientWidth, screen.availWidth / 2);
 | |
| 
 | |
|     this.filter();
 | |
| 
 | |
|     tabPreviewPanelHelper.opening(this);
 | |
| 
 | |
|     this.panel.popupBoxObject.setConsumeRollupEvent(Ci.nsIPopupBoxObject.ROLLUP_NO_CONSUME);
 | |
|     this.panel.openPopup(gBrowser, "overlap", 0, 0, false, true);
 | |
|   },
 | |
| 
 | |
|   close: function allTabs_close() {
 | |
|     this.panel.hidePopup();
 | |
|   },
 | |
| 
 | |
|   setupGUI: function allTabs_setupGUI() {
 | |
|     this.filterField.focus();
 | |
|     this.filterField.placeholder = this.filterField.tooltipText;
 | |
| 
 | |
|     this.panel.addEventListener("keypress", this, false);
 | |
|     this.panel.addEventListener("keypress", this, true);
 | |
|     this._browserCommandSet.addEventListener("command", this, false);
 | |
| 
 | |
|     // When the panel is open, a second click on the all tabs button should
 | |
|     // close the panel but not re-open it.
 | |
|     document.getElementById("Browser:ShowAllTabs").setAttribute("disabled", "true");
 | |
|   },
 | |
| 
 | |
|   suspendGUI: function allTabs_suspendGUI() {
 | |
|     this.filterField.placeholder = "";
 | |
|     this.filterField.value = "";
 | |
|     this._currentFilter = null;
 | |
| 
 | |
|     this._updateTabCloseButton();
 | |
| 
 | |
|     this.panel.removeEventListener("keypress", this, false);
 | |
|     this.panel.removeEventListener("keypress", this, true);
 | |
|     this._browserCommandSet.removeEventListener("command", this, false);
 | |
| 
 | |
|     setTimeout(function () {
 | |
|       document.getElementById("Browser:ShowAllTabs").removeAttribute("disabled");
 | |
|     }, 300);
 | |
|   },
 | |
| 
 | |
|   handleEvent: function allTabs_handleEvent(event) {
 | |
|     if (event.type.startsWith("Tab")) {
 | |
|       var tab = event.target;
 | |
|       if (event.type != "TabOpen")
 | |
|         var preview = this._getPreview(tab);
 | |
|     }
 | |
|     switch (event.type) {
 | |
|       case "TabAttrModified":
 | |
|         // tab attribute modified (e.g. label, crop, busy, image)
 | |
|         if (!preview.hidden)
 | |
|           this._updatePreview(preview);
 | |
|         break;
 | |
|       case "TabOpen":
 | |
|         if (this.isOpen)
 | |
|           this.close();
 | |
|         this._addPreview(tab);
 | |
|         break;
 | |
|       case "TabMove":
 | |
|         let siblingPreview = tab.nextSibling &&
 | |
|                              this._getPreview(tab.nextSibling);
 | |
|         if (siblingPreview)
 | |
|           siblingPreview.parentNode.insertBefore(preview, siblingPreview);
 | |
|         else
 | |
|           this.container.lastChild.appendChild(preview);
 | |
|         if (this.isOpen && !preview.hidden) {
 | |
|           this._reflow();
 | |
|           preview.focus();
 | |
|         }
 | |
|         break;
 | |
|       case "TabClose":
 | |
|         this._removePreview(preview);
 | |
|         break;
 | |
|       case "keypress":
 | |
|         this._onKeyPress(event);
 | |
|         break;
 | |
|       case "command":
 | |
|         if (event.target.id != "Browser:ShowAllTabs") {
 | |
|           // Close the panel when there's a browser command executing in the background.
 | |
|           this.close();
 | |
|         }
 | |
|         break;
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _visible: 0,
 | |
|   _currentFilter: null,
 | |
|   get _stack () {
 | |
|     delete this._stack;
 | |
|     return this._stack = document.getElementById("allTabs-stack");
 | |
|   },
 | |
|   get _browserCommandSet () {
 | |
|     delete this._browserCommandSet;
 | |
|     return this._browserCommandSet = document.getElementById("mainCommandSet");
 | |
|   },
 | |
|   get _previewLabelHeight () {
 | |
|     delete this._previewLabelHeight;
 | |
|     return this._previewLabelHeight = parseInt(getComputedStyle(this.previews[0], "").lineHeight);
 | |
|   },
 | |
| 
 | |
|   get _visiblePreviews ()
 | |
|     Array.filter(this.previews, function (preview) !preview.hidden),
 | |
| 
 | |
|   get _firstVisiblePreview () {
 | |
|     if (this._visible == 0)
 | |
|       return null;
 | |
|     var previews = this.previews;
 | |
|     for (let i = 0; i < previews.length; i++) {
 | |
|       if (!previews[i].hidden)
 | |
|         return previews[i];
 | |
|     }
 | |
|     return null;
 | |
|   },
 | |
| 
 | |
|   _reflow: function allTabs_reflow() {
 | |
|     this._updateTabCloseButton();
 | |
| 
 | |
|     const CONTAINER_MAX_WIDTH = this._maxPanelWidth * .95;
 | |
|     const CONTAINER_MAX_HEIGHT = this._maxPanelHeight - 35;
 | |
|     // the size of the whole preview relative to the thumbnail
 | |
|     const REL_PREVIEW_THUMBNAIL = 1.2;
 | |
|     const REL_PREVIEW_HEIGHT_WIDTH = tabPreviews.height / tabPreviews.width;
 | |
|     const PREVIEW_MAX_WIDTH = tabPreviews.width * REL_PREVIEW_THUMBNAIL;
 | |
| 
 | |
|     var rows, previewHeight, previewWidth, outerHeight;
 | |
|     this._columns = Math.floor(CONTAINER_MAX_WIDTH / PREVIEW_MAX_WIDTH);
 | |
|     do {
 | |
|       rows = Math.ceil(this._visible / this._columns);
 | |
|       previewWidth = Math.min(PREVIEW_MAX_WIDTH,
 | |
|                               Math.round(CONTAINER_MAX_WIDTH / this._columns));
 | |
|       previewHeight = Math.round(previewWidth * REL_PREVIEW_HEIGHT_WIDTH);
 | |
|       outerHeight = previewHeight + this._previewLabelHeight;
 | |
|     } while (rows * outerHeight > CONTAINER_MAX_HEIGHT && ++this._columns);
 | |
| 
 | |
|     var outerWidth = previewWidth;
 | |
|     {
 | |
|       let innerWidth = Math.ceil(previewWidth / REL_PREVIEW_THUMBNAIL);
 | |
|       let innerHeight = Math.ceil(previewHeight / REL_PREVIEW_THUMBNAIL);
 | |
|       var canvasStyle = "max-width:" + innerWidth + "px;" +
 | |
|                         "min-width:" + innerWidth + "px;" +
 | |
|                         "max-height:" + innerHeight + "px;" +
 | |
|                         "min-height:" + innerHeight + "px;";
 | |
|     }
 | |
| 
 | |
|     var previews = Array.slice(this.previews);
 | |
| 
 | |
|     while (this.container.hasChildNodes())
 | |
|       this.container.removeChild(this.container.firstChild);
 | |
|     for (let i = rows || 1; i > 0; i--)
 | |
|       this.container.appendChild(document.createElement("hbox"));
 | |
| 
 | |
|     var row = this.container.firstChild;
 | |
|     var colCount = 0;
 | |
|     previews.forEach(function (preview) {
 | |
|       if (!preview.hidden &&
 | |
|           ++colCount > this._columns) {
 | |
|         row = row.nextSibling;
 | |
|         colCount = 1;
 | |
|       }
 | |
|       preview.setAttribute("minwidth", outerWidth);
 | |
|       preview.setAttribute("height", outerHeight);
 | |
|       preview.setAttribute("canvasstyle", canvasStyle);
 | |
|       preview.removeAttribute("closebuttonhover");
 | |
|       row.appendChild(preview);
 | |
|     }, this);
 | |
| 
 | |
|     this._stack.width = this._maxPanelWidth;
 | |
|     this.container.width = Math.ceil(outerWidth * Math.min(this._columns, this._visible));
 | |
|     this.container.left = Math.round((this._maxPanelWidth - this.container.width) / 2);
 | |
|     this.container.maxWidth = this._maxPanelWidth - this.container.left;
 | |
|     this.container.maxHeight = rows * outerHeight;
 | |
|   },
 | |
| 
 | |
|   _addPreview: function allTabs_addPreview(aTab) {
 | |
|     var preview = document.createElement("button");
 | |
|     preview.className = "allTabs-preview";
 | |
|     preview._tab = aTab;
 | |
|     this.container.lastChild.appendChild(preview);
 | |
|   },
 | |
| 
 | |
|   _removePreview: function allTabs_removePreview(aPreview) {
 | |
|     var updateUI = (this.isOpen && !aPreview.hidden);
 | |
|     aPreview._tab = null;
 | |
|     aPreview.parentNode.removeChild(aPreview);
 | |
|     if (updateUI) {
 | |
|       this._visible--;
 | |
|       this._reflow();
 | |
|       this.filterField.focus();
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _getPreview: function allTabs_getPreview(aTab) {
 | |
|     var previews = this.previews;
 | |
|     for (let i = 0; i < previews.length; i++)
 | |
|       if (previews[i]._tab == aTab)
 | |
|         return previews[i];
 | |
|     return null;
 | |
|   },
 | |
| 
 | |
|   _updateTabCloseButton: function allTabs_updateTabCloseButton(event) {
 | |
|     if (event && event.target == this.tabCloseButton)
 | |
|       return;
 | |
| 
 | |
|     if (this.tabCloseButton._targetPreview) {
 | |
|       if (event && event.target == this.tabCloseButton._targetPreview)
 | |
|         return;
 | |
| 
 | |
|       this.tabCloseButton._targetPreview.removeAttribute("closebuttonhover");
 | |
|     }
 | |
| 
 | |
|     if (event &&
 | |
|         event.target.parentNode.parentNode == this.container &&
 | |
|         (event.target._tab.previousSibling || event.target._tab.nextSibling)) {
 | |
|       let canvas = event.target.firstChild.getBoundingClientRect();
 | |
|       let container = this.container.getBoundingClientRect();
 | |
|       let tabCloseButton = this.tabCloseButton.getBoundingClientRect();
 | |
|       let alignLeft = getComputedStyle(this.panel, "").direction == "rtl";
 | |
| #ifdef XP_MACOSX
 | |
|       alignLeft = !alignLeft;
 | |
| #endif
 | |
|       this.tabCloseButton.left = canvas.left -
 | |
|                                  container.left +
 | |
|                                  parseInt(this.container.left) +
 | |
|                                  (alignLeft ? 0 :
 | |
|                                   canvas.width - tabCloseButton.width);
 | |
|       this.tabCloseButton.top = canvas.top - container.top;
 | |
|       this.tabCloseButton._targetPreview = event.target;
 | |
|       this.tabCloseButton.style.visibility = "visible";
 | |
|       event.target.setAttribute("closebuttonhover", "true");
 | |
|     } else {
 | |
|       this.tabCloseButton.style.visibility = "hidden";
 | |
|       this.tabCloseButton.left = this.tabCloseButton.top = 0;
 | |
|       this.tabCloseButton._targetPreview = null;
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _updatePreview: function allTabs_updatePreview(aPreview) {
 | |
|     aPreview.setAttribute("label", aPreview._tab.label);
 | |
|     aPreview.setAttribute("tooltiptext", aPreview._tab.label);
 | |
|     aPreview.setAttribute("crop", aPreview._tab.crop);
 | |
|     if (aPreview._tab.image)
 | |
|       aPreview.setAttribute("image", aPreview._tab.image);
 | |
|     else
 | |
|       aPreview.removeAttribute("image");
 | |
| 
 | |
|     var thumbnail = tabPreviews.get(aPreview._tab);
 | |
|     if (aPreview.firstChild) {
 | |
|       if (aPreview.firstChild == thumbnail)
 | |
|         return;
 | |
|       aPreview.removeChild(aPreview.firstChild);
 | |
|     }
 | |
|     aPreview.appendChild(thumbnail);
 | |
|   },
 | |
| 
 | |
|   _onKeyPress: function allTabs_onKeyPress(event) {
 | |
|     if (event.eventPhase == event.CAPTURING_PHASE) {
 | |
|       this._onCapturingKeyPress(event);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (event.keyCode == event.DOM_VK_ESCAPE) {
 | |
|       this.close();
 | |
|       event.preventDefault();
 | |
|       event.stopPropagation();
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (event.target == this.filterField) {
 | |
|       switch (event.keyCode) {
 | |
|         case event.DOM_VK_UP:
 | |
|           if (this._visible) {
 | |
|             let previews = this._visiblePreviews;
 | |
|             let columns = Math.min(previews.length, this._columns);
 | |
|             previews[Math.floor(previews.length / columns) * columns - 1].focus();
 | |
|             event.preventDefault();
 | |
|             event.stopPropagation();
 | |
|           }
 | |
|           break;
 | |
|         case event.DOM_VK_DOWN:
 | |
|           if (this._visible) {
 | |
|             this._firstVisiblePreview.focus();
 | |
|             event.preventDefault();
 | |
|             event.stopPropagation();
 | |
|           }
 | |
|           break;
 | |
|       }
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _onCapturingKeyPress: function allTabs_onCapturingKeyPress(event) {
 | |
|     switch (event.keyCode) {
 | |
|       case event.DOM_VK_UP:
 | |
|       case event.DOM_VK_DOWN:
 | |
|         if (event.target != this.filterField)
 | |
|           this._advanceFocusVertically(event);
 | |
|         break;
 | |
|       case event.DOM_VK_RETURN:
 | |
|         if (event.target == this.filterField) {
 | |
|           this.filter();
 | |
|           this.pick();
 | |
|           event.preventDefault();
 | |
|           event.stopPropagation();
 | |
|         }
 | |
|         break;
 | |
|     }
 | |
|   },
 | |
| 
 | |
|   _advanceFocusVertically: function allTabs_advanceFocusVertically(event) {
 | |
|     var preview = document.activeElement;
 | |
|     if (!preview || preview.parentNode.parentNode != this.container)
 | |
|       return;
 | |
| 
 | |
|     event.stopPropagation();
 | |
| 
 | |
|     var up = (event.keyCode == event.DOM_VK_UP);
 | |
|     var previews = this._visiblePreviews;
 | |
| 
 | |
|     if (up && preview == previews[0]) {
 | |
|       this.filterField.focus();
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     var i       = previews.indexOf(preview);
 | |
|     var columns = Math.min(previews.length, this._columns);
 | |
|     var column  = i % columns;
 | |
|     var row     = Math.floor(i / columns);
 | |
| 
 | |
|     function newIndex()    row * columns + column;
 | |
|     function outOfBounds() newIndex() >= previews.length;
 | |
| 
 | |
|     if (up) {
 | |
|       row--;
 | |
|       if (row < 0) {
 | |
|         let rows = Math.ceil(previews.length / columns);
 | |
|         row = rows - 1;
 | |
|         column--;
 | |
|         if (outOfBounds())
 | |
|           row--;
 | |
|       }
 | |
|     } else {
 | |
|       row++;
 | |
|       if (outOfBounds()) {
 | |
|         if (column == columns - 1) {
 | |
|           this.filterField.focus();
 | |
|           return;
 | |
|         }
 | |
|         row = 0;
 | |
|         column++;
 | |
|       }
 | |
|     }
 | |
|     previews[newIndex()].focus();
 | |
|   }
 | |
| };
 | 
