Mypal/application/palemoon/base/content/browser-tabPreviews.js

1143 lines
33 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() {
if (this._selectedTab) {
return;
}
this._selectedTab = gBrowser.selectedTab;
gBrowser.tabContainer.addEventListener("TabSelect", this, false);
gBrowser.tabContainer.addEventListener("SSTabRestored", this, false);
},
get: function(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(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(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() {
return this._selectedIndex < 0 ? document.activeElement : this.previews.item(this._selectedIndex);
},
get isOpen() {
return this.panel.state == "open" || this.panel.state == "showing" || this._timer;
},
get tabCount() {
return this.tabList.length;
},
get tabPreviewCount() {
return Math.min(this.previews.length - 1, this.tabCount);
},
get canvasWidth() {
return Math.min(tabPreviews.width, Math.ceil(screen.availWidth * .85 / this.tabPreviewCount));
},
get canvasHeight() {
return 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]);
}
}
let hidePinnedTabs = gPrefService.getBoolPref("browser.ctrlTab.hidePinnedTabs");
if (hidePinnedTabs) {
regularTabsList = list.filter(function(tab) !tab.pinned);
// Don't hide pinned tabs if we only have 1 regular tab
if (regularTabsList.length > 1) {
list = regularTabsList;
}
}
return this._tabList = list;
},
init: function() {
if (!this._recentlyUsedTabs) {
tabPreviews.init();
this._recentlyUsedTabs = [gBrowser.selectedTab];
this._init(true);
}
},
uninit: function() {
this._recentlyUsedTabs = null;
this._init(false);
},
prefName: "browser.ctrlTab.previews",
readPref: function() {
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() {
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(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(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(aPreview) {
if (this._trackMouseOver)
aPreview.focus();
},
pick: function(aPreview) {
if (!this.tabCount) {
return;
}
var select = (aPreview || this.selected);
if (select == this.showAllButton) {
this.showAllTabs();
} else {
this.close(select._tab);
}
},
showAllTabs: function(aPreview) {
this.close();
document.getElementById("Browser:ShowAllTabs").doCommand();
},
remove: function(aPreview) {
if (aPreview._tab) {
gBrowser.removeTab(aPreview._tab);
}
},
attachTab: function(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(aTab) {
var i = this._recentlyUsedTabs.indexOf(aTab);
if (i >= 0) {
this._recentlyUsedTabs.splice(i, 1);
}
},
open: function() {
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() {
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(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() {
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() {
document.removeEventListener("keyup", this, true);
Array.forEach(this.previews, function(preview) {
this.updatePreview(preview, null);
}, this);
this._tabList = null;
},
onKeyPress: function(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(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(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(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() {
return document.getElementById("alltabs-button");
},
get previews() {
return this.container.getElementsByClassName("allTabs-preview");
},
get isOpen() {
return this.panel.state == "open" || this.panel.state == "showing";
},
init: function() {
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() {
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() {
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(aPreview) {
if (!aPreview) {
aPreview = this._firstVisiblePreview;
}
if (aPreview) {
this.tabToSelect = aPreview._tab;
}
this.close();
},
closeTab: function(event) {
this.filterField.focus();
gBrowser.removeTab(event.currentTarget._targetPreview._tab);
},
filter: function() {
if (this._currentFilter == this.filterField.value) {
return;
}
this._currentFilter = this.filterField.value;
let hidePinnedTabs = gPrefService.getBoolPref("browser.allTabs.hidePinnedTabs");
if (hidePinnedTabs) {
let regularTabsList = Array.filter(this.previews, function(preview) !preview._tab.pinned);
// Show pinned tabs if we don't have any regular tabs
if (regularTabsList.length == 0) {
hidePinnedTabs = false;
}
}
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 || (hidePinnedTabs && tab.pinned)) {
preview.hidden = true;
} else {
this._visible++;
this._updatePreview(preview);
preview.hidden = false;
}
}, this);
this._reflow();
},
open: function() {
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() {
this.panel.hidePopup();
},
setupGUI: function() {
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() {
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(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() {
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(aTab) {
var preview = document.createElement("button");
preview.className = "allTabs-preview";
preview._tab = aTab;
this.container.lastChild.appendChild(preview);
},
_removePreview: function(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(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(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(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(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(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(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();
}
};