gecko-dev/browser/base/content/browser-tabview.js

490 lines
15 KiB
JavaScript

# 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/.
let TabView = {
_deck: null,
_iframe: null,
_window: null,
_initialized: false,
_browserKeyHandlerInitialized: false,
_closedLastVisibleTabBeforeFrameInitialized: false,
_isFrameLoading: false,
_initFrameCallbacks: [],
PREF_BRANCH: "browser.panorama.",
PREF_FIRST_RUN: "browser.panorama.experienced_first_run",
PREF_STARTUP_PAGE: "browser.startup.page",
PREF_RESTORE_ENABLED_ONCE: "browser.panorama.session_restore_enabled_once",
GROUPS_IDENTIFIER: "tabview-groups",
VISIBILITY_IDENTIFIER: "tabview-visibility",
// ----------
get windowTitle() {
delete this.windowTitle;
let brandBundle = document.getElementById("bundle_brand");
let brandShortName = brandBundle.getString("brandShortName");
let title = gNavigatorBundle.getFormattedString("tabview.title", [brandShortName]);
return this.windowTitle = title;
},
// ----------
get firstUseExperienced() {
let pref = this.PREF_FIRST_RUN;
if (Services.prefs.prefHasUserValue(pref))
return Services.prefs.getBoolPref(pref);
return false;
},
// ----------
set firstUseExperienced(val) {
Services.prefs.setBoolPref(this.PREF_FIRST_RUN, val);
},
// ----------
get sessionRestoreEnabledOnce() {
let pref = this.PREF_RESTORE_ENABLED_ONCE;
if (Services.prefs.prefHasUserValue(pref))
return Services.prefs.getBoolPref(pref);
return false;
},
// ----------
set sessionRestoreEnabledOnce(val) {
Services.prefs.setBoolPref(this.PREF_RESTORE_ENABLED_ONCE, val);
},
// ----------
init: function TabView_init() {
// disable the ToggleTabView command for popup windows
goSetCommandEnabled("Browser:ToggleTabView", window.toolbar.visible);
if (!window.toolbar.visible)
return;
if (this._initialized)
return;
if (this.firstUseExperienced) {
// ___ visibility
let sessionstore =
Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
let data = sessionstore.getWindowValue(window, this.VISIBILITY_IDENTIFIER);
if (data && data == "true") {
this.show();
} else {
try {
data = sessionstore.getWindowValue(window, this.GROUPS_IDENTIFIER);
if (data) {
let parsedData = JSON.parse(data);
this.updateGroupNumberBroadcaster(parsedData.totalNumber || 1);
}
} catch (e) { }
let self = this;
// if a tab is changed from hidden to unhidden and the iframe is not
// initialized, load the iframe and setup the tab.
this._tabShowEventListener = function(event) {
if (!self._window)
self._initFrame(function() {
self._window.UI.onTabSelect(gBrowser.selectedTab);
if (self._closedLastVisibleTabBeforeFrameInitialized) {
self._closedLastVisibleTabBeforeFrameInitialized = false;
self._window.UI.showTabView(false);
}
});
};
this._tabCloseEventListener = function(event) {
if (!self._window && gBrowser.visibleTabs.length == 0)
self._closedLastVisibleTabBeforeFrameInitialized = true;
};
gBrowser.tabContainer.addEventListener(
"TabShow", this._tabShowEventListener, false);
gBrowser.tabContainer.addEventListener(
"TabClose", this._tabCloseEventListener, false);
if (this._tabBrowserHasHiddenTabs()) {
this._setBrowserKeyHandlers();
} else {
// for restoring last session and undoing recently closed window
this._SSWindowStateReadyListener = function (event) {
if (this._tabBrowserHasHiddenTabs())
this._setBrowserKeyHandlers();
}.bind(this);
window.addEventListener(
"SSWindowStateReady", this._SSWindowStateReadyListener, false);
}
}
}
Services.prefs.addObserver(this.PREF_BRANCH, this, false);
this._initialized = true;
},
// ----------
// Observes topic changes.
observe: function TabView_observe(subject, topic, data) {
if (data == this.PREF_FIRST_RUN && this.firstUseExperienced) {
this._addToolbarButton();
this.enableSessionRestore();
}
},
// ----------
// Uninitializes TabView.
uninit: function TabView_uninit() {
if (!this._initialized)
return;
Services.prefs.removeObserver(this.PREF_BRANCH, this);
if (this._tabShowEventListener)
gBrowser.tabContainer.removeEventListener(
"TabShow", this._tabShowEventListener, false);
if (this._tabCloseEventListener)
gBrowser.tabContainer.removeEventListener(
"TabClose", this._tabCloseEventListener, false);
if (this._SSWindowStateReadyListener)
window.removeEventListener(
"SSWindowStateReady", this._SSWindowStateReadyListener, false);
this._initialized = false;
},
// ----------
// Creates the frame and calls the callback once it's loaded.
// If the frame already exists, calls the callback immediately.
_initFrame: function TabView__initFrame(callback) {
let hasCallback = typeof callback == "function";
// prevent frame to be initialized for popup windows
if (!window.toolbar.visible)
return;
if (this._window) {
if (hasCallback)
callback();
return;
}
if (hasCallback)
this._initFrameCallbacks.push(callback);
if (this._isFrameLoading)
return;
this._isFrameLoading = true;
TelemetryStopwatch.start("PANORAMA_INITIALIZATION_TIME_MS");
// ___ find the deck
this._deck = document.getElementById("tab-view-deck");
// ___ create the frame
this._iframe = document.createElement("iframe");
this._iframe.id = "tab-view";
this._iframe.setAttribute("transparent", "true");
this._iframe.setAttribute("tooltip", "tab-view-tooltip");
this._iframe.flex = 1;
let self = this;
window.addEventListener("tabviewframeinitialized", function onInit() {
window.removeEventListener("tabviewframeinitialized", onInit, false);
TelemetryStopwatch.finish("PANORAMA_INITIALIZATION_TIME_MS");
self._isFrameLoading = false;
self._window = self._iframe.contentWindow;
self._setBrowserKeyHandlers();
if (self._tabShowEventListener) {
gBrowser.tabContainer.removeEventListener(
"TabShow", self._tabShowEventListener, false);
self._tabShowEventListener = null;
}
if (self._tabCloseEventListener) {
gBrowser.tabContainer.removeEventListener(
"TabClose", self._tabCloseEventListener, false);
self._tabCloseEventListener = null;
}
if (self._SSWindowStateReadyListener) {
window.removeEventListener(
"SSWindowStateReady", self._SSWindowStateReadyListener, false);
self._SSWindowStateReadyListener = null;
}
self._initFrameCallbacks.forEach(function (cb) cb());
self._initFrameCallbacks = [];
}, false);
this._iframe.setAttribute("src", "chrome://browser/content/tabview.html");
this._deck.appendChild(this._iframe);
// ___ create tooltip
let tooltip = document.createElement("tooltip");
tooltip.id = "tab-view-tooltip";
tooltip.setAttribute("onpopupshowing", "return TabView.fillInTooltip(document.tooltipNode);");
document.getElementById("mainPopupSet").appendChild(tooltip);
},
// ----------
getContentWindow: function TabView_getContentWindow() {
return this._window;
},
// ----------
isVisible: function TabView_isVisible() {
return (this._deck ? this._deck.selectedPanel == this._iframe : false);
},
// ----------
show: function TabView_show() {
if (this.isVisible())
return;
let self = this;
this._initFrame(function() {
self._window.UI.showTabView(true);
});
},
// ----------
hide: function TabView_hide() {
if (!this.isVisible())
return;
this._window.UI.exit();
},
// ----------
toggle: function TabView_toggle() {
if (this.isVisible())
this.hide();
else
this.show();
},
// ----------
_tabBrowserHasHiddenTabs: function TabView_tabBrowserHasHiddenTabs() {
return (gBrowser.tabs.length - gBrowser.visibleTabs.length) > 0;
},
// ----------
updateContextMenu: function TabView_updateContextMenu(tab, popup) {
let separator = document.getElementById("context_tabViewNamedGroups");
let isEmpty = true;
while (popup.firstChild && popup.firstChild != separator)
popup.removeChild(popup.firstChild);
let self = this;
this._initFrame(function() {
let activeGroup = tab._tabViewTabItem.parent;
let groupItems = self._window.GroupItems.groupItems;
groupItems.forEach(function(groupItem) {
// if group has title, it's not hidden and there is no active group or
// the active group id doesn't match the group id, a group menu item
// would be added.
if (!groupItem.hidden &&
(groupItem.getTitle().trim() || groupItem.getChildren().length) &&
(!activeGroup || activeGroup.id != groupItem.id)) {
let menuItem = self._createGroupMenuItem(groupItem);
popup.insertBefore(menuItem, separator);
isEmpty = false;
}
});
separator.hidden = isEmpty;
});
},
// ----------
_createGroupMenuItem: function TabView__createGroupMenuItem(groupItem) {
let menuItem = document.createElement("menuitem");
let title = groupItem.getTitle();
if (!title.trim()) {
let topChildLabel = groupItem.getTopChild().tab.label;
let childNum = groupItem.getChildren().length;
if (childNum > 1) {
let num = childNum - 1;
title =
gNavigatorBundle.getString("tabview.moveToUnnamedGroup.label");
title = PluralForm.get(num, title).replace("#1", topChildLabel).replace("#2", num);
} else {
title = topChildLabel;
}
}
menuItem.setAttribute("label", title);
menuItem.setAttribute("tooltiptext", title);
menuItem.setAttribute("crop", "center");
menuItem.setAttribute("class", "tabview-menuitem");
menuItem.setAttribute(
"oncommand",
"TabView.moveTabTo(TabContextMenu.contextTab,'" + groupItem.id + "')");
return menuItem;
},
// ----------
moveTabTo: function TabView_moveTabTo(tab, groupItemId) {
if (this._window) {
this._window.GroupItems.moveTabToGroupItem(tab, groupItemId);
} else {
let self = this;
this._initFrame(function() {
self._window.GroupItems.moveTabToGroupItem(tab, groupItemId);
});
}
},
// ----------
// Adds new key commands to the browser, for invoking the Tab Candy UI
// and for switching between groups of tabs when outside of the Tab Candy UI.
_setBrowserKeyHandlers: function TabView__setBrowserKeyHandlers() {
if (this._browserKeyHandlerInitialized)
return;
this._browserKeyHandlerInitialized = true;
let self = this;
window.addEventListener("keypress", function(event) {
if (self.isVisible() || !self._tabBrowserHasHiddenTabs())
return;
let charCode = event.charCode;
// Control (+ Shift) + `
if (event.ctrlKey && !event.metaKey && !event.altKey &&
(charCode == 96 || charCode == 126)) {
event.stopPropagation();
event.preventDefault();
self._initFrame(function() {
let groupItems = self._window.GroupItems;
let tabItem = groupItems.getNextGroupItemTab(event.shiftKey);
if (!tabItem)
return;
if (gBrowser.selectedTab.pinned)
groupItems.updateActiveGroupItemAndTabBar(tabItem, {dontSetActiveTabInGroup: true});
else
gBrowser.selectedTab = tabItem.tab;
});
}
}, true);
},
// ----------
// Prepares the tab view for undo close tab.
prepareUndoCloseTab: function TabView_prepareUndoCloseTab(blankTabToRemove) {
if (this._window) {
this._window.UI.restoredClosedTab = true;
if (blankTabToRemove && blankTabToRemove._tabViewTabItem)
blankTabToRemove._tabViewTabItem.isRemovedAfterRestore = true;
}
},
// ----------
// Cleans up the tab view after undo close tab.
afterUndoCloseTab: function TabView_afterUndoCloseTab() {
if (this._window)
this._window.UI.restoredClosedTab = false;
},
// ----------
// On move to group pop showing.
moveToGroupPopupShowing: function TabView_moveToGroupPopupShowing(event) {
// Update the context menu only if Panorama was already initialized or if
// there are hidden tabs.
let numHiddenTabs = gBrowser.tabs.length - gBrowser.visibleTabs.length;
if (this._window || numHiddenTabs > 0)
this.updateContextMenu(TabContextMenu.contextTab, event.target);
},
// ----------
// Function: _addToolbarButton
// Adds the TabView button to the TabsToolbar.
_addToolbarButton: function TabView__addToolbarButton() {
let buttonId = "tabview-button";
if (document.getElementById(buttonId))
return;
let toolbar = document.getElementById("TabsToolbar");
let currentSet = toolbar.currentSet.split(",");
let alltabsPos = currentSet.indexOf("alltabs-button");
if (-1 == alltabsPos)
return;
currentSet[alltabsPos] += "," + buttonId;
currentSet = currentSet.join(",");
toolbar.currentSet = currentSet;
toolbar.setAttribute("currentset", currentSet);
document.persist(toolbar.id, "currentset");
},
// ----------
// Function: updateGroupNumberBroadcaster
// Updates the group number broadcaster.
updateGroupNumberBroadcaster: function TabView_updateGroupNumberBroadcaster(number) {
let groupsNumber = document.getElementById("tabviewGroupsNumber");
groupsNumber.setAttribute("groups", number);
},
// ----------
// Function: enableSessionRestore
// Enables automatic session restore when the browser is started. Does
// nothing if we already did that once in the past.
enableSessionRestore: function TabView_enableSessionRestore() {
if (!this._window || !this.firstUseExperienced)
return;
// do nothing if we already enabled session restore once
if (this.sessionRestoreEnabledOnce)
return;
this.sessionRestoreEnabledOnce = true;
// enable session restore if necessary
if (Services.prefs.getIntPref(this.PREF_STARTUP_PAGE) != 3) {
Services.prefs.setIntPref(this.PREF_STARTUP_PAGE, 3);
// show banner
this._window.UI.notifySessionRestoreEnabled();
}
},
// ----------
// Function: fillInTooltip
// Fills in the tooltip text.
fillInTooltip: function fillInTooltip(tipElement) {
let retVal = false;
let titleText = null;
let direction = tipElement.ownerDocument.dir;
while (!titleText && tipElement) {
if (tipElement.nodeType == Node.ELEMENT_NODE)
titleText = tipElement.getAttribute("title");
tipElement = tipElement.parentNode;
}
let tipNode = document.getElementById("tab-view-tooltip");
tipNode.style.direction = direction;
if (titleText) {
tipNode.setAttribute("label", titleText);
retVal = true;
}
return retVal;
}
};