summaryrefslogtreecommitdiffstats
path: root/base/content/browser-tabPreviews.js
diff options
context:
space:
mode:
authorThomas Groman <tgroman@nuegia.net>2019-12-16 19:48:42 -0800
committerThomas Groman <tgroman@nuegia.net>2019-12-16 19:48:42 -0800
commit4492b5f8e774bf3b4f21e4e468fc052cbcbb468a (patch)
tree37970571a7dcbeb6b58c991ce718ce7001ac97d6 /base/content/browser-tabPreviews.js
downloadwebbrowser-4492b5f8e774bf3b4f21e4e468fc052cbcbb468a.tar
webbrowser-4492b5f8e774bf3b4f21e4e468fc052cbcbb468a.tar.gz
webbrowser-4492b5f8e774bf3b4f21e4e468fc052cbcbb468a.tar.lz
webbrowser-4492b5f8e774bf3b4f21e4e468fc052cbcbb468a.tar.xz
webbrowser-4492b5f8e774bf3b4f21e4e468fc052cbcbb468a.zip
initial commit
Diffstat (limited to 'base/content/browser-tabPreviews.js')
-rw-r--r--base/content/browser-tabPreviews.js1058
1 files changed, 1058 insertions, 0 deletions
diff --git a/base/content/browser-tabPreviews.js b/base/content/browser-tabPreviews.js
new file mode 100644
index 0000000..e0755b8
--- /dev/null
+++ b/base/content/browser-tabPreviews.js
@@ -0,0 +1,1058 @@
+/*
+#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.linkedBrowser /* bug 795608: the tab may got removed while drawing the thumbnail */) {
+ 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") &&
+ !aTab.hasAttribute("pending"))
+ 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) {
+ let selectedIndex = Array.indexOf(this.previews, this.selected);
+ do {
+ selectedIndex += aForward ? 1 : -1;
+ if (selectedIndex < 0)
+ selectedIndex = this.previews.length - 1;
+ else if (selectedIndex >= this.previews.length)
+ selectedIndex = 0;
+ } while (this.previews[selectedIndex].hidden);
+
+ if (this._selectedIndex == -1) {
+ // Focus is already in the panel.
+ this.previews[selectedIndex].focus();
+ } else {
+ this._selectedIndex = selectedIndex;
+ }
+
+ 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 = tabs[0].selected ? 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.selected && 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.includes(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(PopupBoxObject.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");
+
+ aPreview.removeAttribute("soundplaying");
+ aPreview.removeAttribute("muted");
+ if (aPreview._tab.hasAttribute("muted"))
+ aPreview.setAttribute("muted", "true");
+ else if (aPreview._tab.hasAttribute("soundplaying"))
+ aPreview.setAttribute("soundplaying", "true");
+
+ 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();
+ }
+};