Revision control

Copy as Markdown

Other Tools

/* 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/. */
var gToolboxDocument = null;
var gToolbox = null;
var gCurrentDragOverItem = null;
var gToolboxChanged = false;
var gToolboxSheet = false;
var gPaletteBox = null;
var { AppConstants } = ChromeUtils.import(
);
function onLoad() {
if ("arguments" in window && window.arguments[0]) {
InitWithToolbox(window.arguments[0]);
repositionDialog(window);
} else if (window.frameElement && "toolbox" in window.frameElement) {
gToolboxSheet = true;
InitWithToolbox(window.frameElement.toolbox);
repositionDialog(window.frameElement.panel);
}
}
function InitWithToolbox(aToolbox) {
gToolbox = aToolbox;
dispatchCustomizationEvent("beforecustomization");
gToolboxDocument = gToolbox.ownerDocument;
gToolbox.customizing = true;
forEachCustomizableToolbar(function(toolbar) {
toolbar.setAttribute("customizing", "true");
});
gPaletteBox = document.getElementById("palette-box");
var elts = getRootElements();
for (let i = 0; i < elts.length; i++) {
elts[i].addEventListener("dragstart", onToolbarDragStart, true);
elts[i].addEventListener("dragover", onToolbarDragOver, true);
elts[i].addEventListener("dragexit", onToolbarDragExit, true);
elts[i].addEventListener("drop", onToolbarDrop, true);
}
initDialog();
}
function onClose() {
if (!gToolboxSheet) {
window.close();
} else {
finishToolbarCustomization();
}
}
function onUnload() {
if (!gToolboxSheet) {
finishToolbarCustomization();
}
}
function finishToolbarCustomization() {
removeToolboxListeners();
unwrapToolbarItems();
persistCurrentSets();
gToolbox.customizing = false;
forEachCustomizableToolbar(function(toolbar) {
toolbar.removeAttribute("customizing");
});
notifyParentComplete();
}
function initDialog() {
var mode = gToolbox.getAttribute("mode");
document.getElementById("modelist").value = mode;
var smallIconsCheckbox = document.getElementById("smallicons");
smallIconsCheckbox.checked = gToolbox.getAttribute("iconsize") == "small";
if (mode == "text") {
smallIconsCheckbox.disabled = true;
}
if (AppConstants.MOZ_APP_NAME == "thunderbird") {
document.getElementById(
"showTitlebar"
).checked = !Services.prefs.getBoolPref("mail.tabs.drawInTitlebar");
document.getElementById(
"showDragSpace"
).checked = Services.prefs.getBoolPref("mail.tabs.extraDragSpace");
if (
window.opener &&
window.opener.document.documentElement.getAttribute("windowtype") ==
"mail:3pane"
) {
document.getElementById("titlebarSettings").hidden = false;
}
}
// Build up the palette of other items.
buildPalette();
// Wrap all the items on the toolbar in toolbarpaletteitems.
wrapToolbarItems();
}
function repositionDialog(aWindow) {
// Position the dialog touching the bottom of the toolbox and centered with
// it.
if (!aWindow) {
return;
}
var width;
if (aWindow != window) {
width = aWindow.getBoundingClientRect().width;
} else if (document.documentElement.hasAttribute("width")) {
width = document.documentElement.getAttribute("width");
} else {
width = parseInt(document.documentElement.style.width);
}
var boundingRect = gToolbox.getBoundingClientRect();
var screenX = gToolbox.screenX + (boundingRect.width - width) / 2;
var screenY = gToolbox.screenY + boundingRect.height;
aWindow.moveTo(screenX, screenY);
}
function removeToolboxListeners() {
var elts = getRootElements();
for (let i = 0; i < elts.length; i++) {
elts[i].removeEventListener("dragstart", onToolbarDragStart, true);
elts[i].removeEventListener("dragover", onToolbarDragOver, true);
elts[i].removeEventListener("dragexit", onToolbarDragExit, true);
elts[i].removeEventListener("drop", onToolbarDrop, true);
}
}
/**
* Invoke a callback on the toolbox to notify it that the dialog is done
* and going away.
*/
function notifyParentComplete() {
if ("customizeDone" in gToolbox) {
gToolbox.customizeDone(gToolboxChanged);
}
dispatchCustomizationEvent("aftercustomization");
}
function toolboxChanged(aType) {
gToolboxChanged = true;
if ("customizeChange" in gToolbox) {
gToolbox.customizeChange(aType);
}
dispatchCustomizationEvent("customizationchange");
}
function dispatchCustomizationEvent(aEventName) {
var evt = document.createEvent("Events");
evt.initEvent(aEventName, true, true);
gToolbox.dispatchEvent(evt);
}
/**
* Persist the current set of buttons in all customizable toolbars to
* localstore.
*/
function persistCurrentSets() {
if (!gToolboxChanged || gToolboxDocument.defaultView.closed) {
return;
}
forEachCustomizableToolbar(function(toolbar) {
// Calculate currentset and store it in the attribute.
var currentSet = toolbar.currentSet;
toolbar.setAttribute("currentset", currentSet);
Services.xulStore.persist(toolbar, "currentset");
});
}
/**
* Wraps all items in all customizable toolbars in a toolbox.
*/
function wrapToolbarItems() {
forEachCustomizableToolbar(function(toolbar) {
for (let item of toolbar.children) {
if (AppConstants.platform == "macosx") {
if (
item.firstElementChild &&
item.firstElementChild.localName == "menubar"
) {
return;
}
}
if (isToolbarItem(item)) {
let wrapper = wrapToolbarItem(item);
cleanupItemForToolbar(item, wrapper);
}
}
});
}
function getRootElements() {
if (window.frameElement && "externalToolbars" in window.frameElement) {
return [gToolbox].concat(window.frameElement.externalToolbars);
}
if ("arguments" in window && window.arguments[1].length > 0) {
return [gToolbox].concat(window.arguments[1]);
}
return [gToolbox];
}
/**
* Unwraps all items in all customizable toolbars in a toolbox.
*/
function unwrapToolbarItems() {
let elts = getRootElements();
for (let i = 0; i < elts.length; i++) {
let paletteItems = elts[i].getElementsByTagName("toolbarpaletteitem");
let paletteItem;
while ((paletteItem = paletteItems.item(0)) != null) {
let toolbarItem = paletteItem.firstElementChild;
restoreItemForToolbar(toolbarItem, paletteItem);
paletteItem.parentNode.replaceChild(toolbarItem, paletteItem);
}
}
}
/**
* Creates a wrapper that can be used to contain a toolbaritem and prevent
* it from receiving UI events.
*/
function createWrapper(aId, aDocument) {
let wrapper = aDocument.createXULElement("toolbarpaletteitem");
wrapper.id = "wrapper-" + aId;
return wrapper;
}
/**
* Wraps an item that has been cloned from a template and adds
* it to the end of the palette.
*/
function wrapPaletteItem(aPaletteItem) {
var wrapper = createWrapper(aPaletteItem.id, document);
wrapper.appendChild(aPaletteItem);
// XXX We need to call this AFTER the palette item has been appended
// to the wrapper or else we crash dropping certain buttons on the
// palette due to removal of the command and disabled attributes - JRH
cleanUpItemForPalette(aPaletteItem, wrapper);
gPaletteBox.appendChild(wrapper);
}
/**
* Wraps an item that is currently on a toolbar and replaces the item
* with the wrapper. This is not used when dropping items from the palette,
* only when first starting the dialog and wrapping everything on the toolbars.
*/
function wrapToolbarItem(aToolbarItem) {
var wrapper = createWrapper(aToolbarItem.id, gToolboxDocument);
wrapper.flex = aToolbarItem.flex;
aToolbarItem.parentNode.replaceChild(wrapper, aToolbarItem);
wrapper.appendChild(aToolbarItem);
return wrapper;
}
/**
* Get the list of ids for the current set of items on each toolbar.
*/
function getCurrentItemIds() {
var currentItems = {};
forEachCustomizableToolbar(function(toolbar) {
var child = toolbar.firstElementChild;
while (child) {
if (isToolbarItem(child)) {
currentItems[child.id] = 1;
}
child = child.nextElementSibling;
}
});
return currentItems;
}
/**
* Builds the palette of draggable items that are not yet in a toolbar.
*/
function buildPalette() {
// Empty the palette first.
while (gPaletteBox.lastElementChild) {
gPaletteBox.lastChild.remove();
}
// Add the toolbar separator item.
var templateNode = document.createXULElement("toolbarseparator");
templateNode.id = "separator";
wrapPaletteItem(templateNode);
// Add the toolbar spring item.
templateNode = document.createXULElement("toolbarspring");
templateNode.id = "spring";
templateNode.flex = 1;
wrapPaletteItem(templateNode);
// Add the toolbar spacer item.
templateNode = document.createXULElement("toolbarspacer");
templateNode.id = "spacer";
templateNode.flex = 1;
wrapPaletteItem(templateNode);
var currentItems = getCurrentItemIds();
templateNode = gToolbox.palette.firstElementChild;
while (templateNode) {
// Check if the item is already in a toolbar before adding it to the
// palette, but do not add back separators, springs and spacers - we do
// not want them duplicated.
if (!isSpecialItem(templateNode) && !(templateNode.id in currentItems)) {
var paletteItem = document.importNode(templateNode, true);
wrapPaletteItem(paletteItem);
}
templateNode = templateNode.nextElementSibling;
}
}
/**
* Makes sure that an item that has been cloned from a template
* is stripped of any attributes that may adversely affect its
* appearance in the palette.
*/
function cleanUpItemForPalette(aItem, aWrapper) {
aWrapper.setAttribute("place", "palette");
setWrapperType(aItem, aWrapper);
if (aItem.hasAttribute("title")) {
aWrapper.setAttribute("title", aItem.getAttribute("title"));
} else if (aItem.hasAttribute("label")) {
aWrapper.setAttribute("title", aItem.getAttribute("label"));
} else if (isSpecialItem(aItem)) {
var stringBundle = document.getElementById("stringBundle");
// Remove the common "toolbar" prefix to generate the string name.
var title = stringBundle.getString(aItem.localName.slice(7) + "Title");
aWrapper.setAttribute("title", title);
}
aWrapper.setAttribute("tooltiptext", aWrapper.getAttribute("title"));
// Remove attributes that screw up our appearance.
aItem.removeAttribute("command");
aItem.removeAttribute("observes");
aItem.removeAttribute("type");
aItem.removeAttribute("width");
aItem.removeAttribute("checked");
aItem.removeAttribute("collapsed");
aWrapper.querySelectorAll("[disabled]").forEach(function(aNode) {
aNode.removeAttribute("disabled");
});
}
/**
* Makes sure that an item that has been cloned from a template
* is stripped of all properties that may adversely affect its
* appearance in the toolbar. Store critical properties on the
* wrapper so they can be put back on the item when we're done.
*/
function cleanupItemForToolbar(aItem, aWrapper) {
setWrapperType(aItem, aWrapper);
aWrapper.setAttribute("place", "toolbar");
if (aItem.hasAttribute("command")) {
aWrapper.setAttribute("itemcommand", aItem.getAttribute("command"));
aItem.removeAttribute("command");
}
if (aItem.hasAttribute("collapsed")) {
aWrapper.setAttribute("itemcollapsed", aItem.getAttribute("collapsed"));
aItem.removeAttribute("collapsed");
}
if (aItem.checked) {
aWrapper.setAttribute("itemchecked", "true");
aItem.checked = false;
}
if (aItem.disabled) {
aWrapper.setAttribute("itemdisabled", "true");
aItem.disabled = false;
}
}
/**
* Restore all the properties that we stripped off above.
*/
function restoreItemForToolbar(aItem, aWrapper) {
if (aWrapper.hasAttribute("itemdisabled")) {
aItem.disabled = true;
}
if (aWrapper.hasAttribute("itemchecked")) {
aItem.checked = true;
}
if (aWrapper.hasAttribute("itemcollapsed")) {
let collapsed = aWrapper.getAttribute("itemcollapsed");
aItem.setAttribute("collapsed", collapsed);
}
if (aWrapper.hasAttribute("itemcommand")) {
let commandID = aWrapper.getAttribute("itemcommand");
aItem.setAttribute("command", commandID);
// XXX Bug 309953 - toolbarbuttons aren't in sync with their commands after customizing
let command = gToolboxDocument.getElementById(commandID);
if (command && command.hasAttribute("disabled")) {
aItem.setAttribute("disabled", command.getAttribute("disabled"));
}
}
}
function setWrapperType(aItem, aWrapper) {
if (aItem.localName == "toolbarseparator") {
aWrapper.setAttribute("type", "separator");
} else if (aItem.localName == "toolbarspring") {
aWrapper.setAttribute("type", "spring");
} else if (aItem.localName == "toolbarspacer") {
aWrapper.setAttribute("type", "spacer");
} else if (aItem.localName == "toolbaritem" && aItem.firstElementChild) {
aWrapper.setAttribute("type", aItem.firstElementChild.localName);
}
}
function setDragActive(aItem, aValue) {
var node = aItem;
var direction = window.getComputedStyle(aItem).direction;
var value = direction == "ltr" ? "left" : "right";
if (aItem.localName == "toolbar") {
node = aItem.lastElementChild;
value = direction == "ltr" ? "right" : "left";
}
if (!node) {
return;
}
if (aValue) {
if (!node.hasAttribute("dragover")) {
node.setAttribute("dragover", value);
}
} else {
node.removeAttribute("dragover");
}
}
/**
* Restore the default set of buttons to fixed toolbars,
* remove all custom toolbars, and rebuild the palette.
*/
function restoreDefaultSet() {
// Unwrap the items on the toolbar.
unwrapToolbarItems();
// Remove all of the customized toolbars.
var child = gToolbox.lastElementChild;
while (child) {
if (child.hasAttribute("customindex")) {
var thisChild = child;
child = child.previousElementSibling;
thisChild.currentSet = "__empty";
gToolbox.removeChild(thisChild);
} else {
child = child.previousElementSibling;
}
}
// Restore the defaultset for fixed toolbars.
forEachCustomizableToolbar(function(toolbar) {
var defaultSet = toolbar.getAttribute("defaultset");
if (defaultSet) {
toolbar.currentSet = defaultSet;
}
});
// Restore the default icon size and mode.
document.getElementById("smallicons").checked = updateIconSize() == "small";
document.getElementById("modelist").value = updateToolbarMode();
// Now rebuild the palette.
buildPalette();
// Now re-wrap the items on the toolbar.
wrapToolbarItems();
toolboxChanged("reset");
}
function updateIconSize(aSize) {
return updateToolboxProperty("iconsize", aSize, "large");
}
function updateTitlebar() {
let titlebarCheckbox = document.getElementById("showTitlebar");
Services.prefs.setBoolPref(
"mail.tabs.drawInTitlebar",
!titlebarCheckbox.checked
);
// Bring the customizeToolbar window to front (on linux it's behind the main
// window). Otherwise the customization window gets left in the background.
setTimeout(() => window.focus(), 100);
}
function updateDragSpace() {
let dragSpaceCheckbox = document.getElementById("showDragSpace");
Services.prefs.setBoolPref(
"mail.tabs.extraDragSpace",
dragSpaceCheckbox.checked
);
// Bring the customizeToolbar window to front (on linux it's behind the main
// window). Otherwise the customization window gets left in the background.
setTimeout(() => window.focus(), 100);
}
function updateToolbarMode(aModeValue) {
var mode = updateToolboxProperty("mode", aModeValue, "icons");
var iconSizeCheckbox = document.getElementById("smallicons");
iconSizeCheckbox.disabled = mode == "text";
return mode;
}
function updateToolboxProperty(aProp, aValue, aToolkitDefault) {
var toolboxDefault =
gToolbox.getAttribute("default" + aProp) || aToolkitDefault;
gToolbox.setAttribute(aProp, aValue || toolboxDefault);
Services.xulStore.persist(gToolbox, aProp);
forEachCustomizableToolbar(function(toolbar) {
var toolbarDefault =
toolbar.getAttribute("default" + aProp) || toolboxDefault;
if (
toolbar.getAttribute("lock" + aProp) == "true" &&
toolbar.getAttribute(aProp) == toolbarDefault
) {
return;
}
toolbar.setAttribute(aProp, aValue || toolbarDefault);
Services.xulStore.persist(toolbar, aProp);
});
toolboxChanged(aProp);
return aValue || toolboxDefault;
}
function forEachCustomizableToolbar(callback) {
if (window.frameElement && "externalToolbars" in window.frameElement) {
Array.from(window.frameElement.externalToolbars)
.filter(isCustomizableToolbar)
.forEach(callback);
} else if ("arguments" in window && window.arguments[1].length > 0) {
Array.from(window.arguments[1])
.filter(isCustomizableToolbar)
.forEach(callback);
}
Array.from(gToolbox.children)
.filter(isCustomizableToolbar)
.forEach(callback);
}
function isCustomizableToolbar(aElt) {
return (
aElt.localName == "toolbar" && aElt.getAttribute("customizable") == "true"
);
}
function isSpecialItem(aElt) {
return (
aElt.localName == "toolbarseparator" ||
aElt.localName == "toolbarspring" ||
aElt.localName == "toolbarspacer"
);
}
function isToolbarItem(aElt) {
return (
aElt.localName == "toolbarbutton" ||
aElt.localName == "toolbaritem" ||
aElt.localName == "toolbarseparator" ||
aElt.localName == "toolbarspring" ||
aElt.localName == "toolbarspacer"
);
}
// Drag and Drop observers
function onToolbarDragExit(aEvent) {
if (isUnwantedDragEvent(aEvent)) {
return;
}
if (gCurrentDragOverItem) {
setDragActive(gCurrentDragOverItem, false);
}
}
function onToolbarDragStart(aEvent) {
var item = aEvent.target;
while (item && item.localName != "toolbarpaletteitem") {
if (item.localName == "toolbar") {
return;
}
item = item.parentNode;
}
item.setAttribute("dragactive", "true");
var dt = aEvent.dataTransfer;
var documentId = gToolboxDocument.documentElement.id;
dt.setData("text/toolbarwrapper-id/" + documentId, item.firstElementChild.id);
dt.effectAllowed = "move";
}
function onToolbarDragOver(aEvent) {
if (isUnwantedDragEvent(aEvent)) {
return;
}
var documentId = gToolboxDocument.documentElement.id;
if (
!aEvent.dataTransfer.types.includes(
"text/toolbarwrapper-id/" + documentId.toLowerCase()
)
) {
return;
}
var toolbar = aEvent.target;
var dropTarget = aEvent.target;
while (toolbar && toolbar.localName != "toolbar") {
dropTarget = toolbar;
toolbar = toolbar.parentNode;
}
// Make sure we are dragging over a customizable toolbar.
if (!toolbar || !isCustomizableToolbar(toolbar)) {
gCurrentDragOverItem = null;
return;
}
var previousDragItem = gCurrentDragOverItem;
if (dropTarget.localName == "toolbar") {
gCurrentDragOverItem = dropTarget;
} else {
gCurrentDragOverItem = null;
var direction = window.getComputedStyle(dropTarget.parentNode).direction;
var boundingRect = dropTarget.getBoundingClientRect();
var dropTargetCenter = boundingRect.x + boundingRect.width / 2;
var dragAfter;
if (direction == "ltr") {
dragAfter = aEvent.clientX > dropTargetCenter;
} else {
dragAfter = aEvent.clientX < dropTargetCenter;
}
if (dragAfter) {
gCurrentDragOverItem = dropTarget.nextElementSibling;
if (!gCurrentDragOverItem) {
gCurrentDragOverItem = toolbar;
}
} else {
gCurrentDragOverItem = dropTarget;
}
}
if (previousDragItem && gCurrentDragOverItem != previousDragItem) {
setDragActive(previousDragItem, false);
}
setDragActive(gCurrentDragOverItem, true);
aEvent.preventDefault();
aEvent.stopPropagation();
}
function onToolbarDrop(aEvent) {
if (isUnwantedDragEvent(aEvent)) {
return;
}
if (!gCurrentDragOverItem) {
return;
}
setDragActive(gCurrentDragOverItem, false);
var documentId = gToolboxDocument.documentElement.id;
var draggedItemId = aEvent.dataTransfer.getData(
"text/toolbarwrapper-id/" + documentId
);
if (gCurrentDragOverItem.id == draggedItemId) {
return;
}
var toolbar = aEvent.target;
while (toolbar.localName != "toolbar") {
toolbar = toolbar.parentNode;
}
var draggedPaletteWrapper = document.getElementById(
"wrapper-" + draggedItemId
);
if (!draggedPaletteWrapper) {
// The wrapper has been dragged from the toolbar.
// Get the wrapper from the toolbar document and make sure that
// it isn't being dropped on itself.
let wrapper = gToolboxDocument.getElementById("wrapper-" + draggedItemId);
if (wrapper == gCurrentDragOverItem) {
return;
}
// Don't allow non-removable kids (e.g., the menubar) to move.
if (wrapper.firstElementChild.getAttribute("removable") != "true") {
return;
}
// Remove the item from its place in the toolbar.
wrapper.remove();
// Determine which toolbar we are dropping on.
var dropToolbar = null;
if (gCurrentDragOverItem.localName == "toolbar") {
dropToolbar = gCurrentDragOverItem;
} else {
dropToolbar = gCurrentDragOverItem.parentNode;
}
// Insert the item into the toolbar.
if (gCurrentDragOverItem != dropToolbar) {
dropToolbar.insertBefore(wrapper, gCurrentDragOverItem);
} else {
dropToolbar.appendChild(wrapper);
}
} else {
// The item has been dragged from the palette
// Create a new wrapper for the item. We don't know the id yet.
let wrapper = createWrapper("", gToolboxDocument);
// Ask the toolbar to clone the item's template, place it inside the wrapper, and insert it in the toolbar.
var newItem = toolbar.insertItem(
draggedItemId,
gCurrentDragOverItem == toolbar ? null : gCurrentDragOverItem,
wrapper
);
// Prepare the item and wrapper to look good on the toolbar.
cleanupItemForToolbar(newItem, wrapper);
wrapper.id = "wrapper-" + newItem.id;
wrapper.flex = newItem.flex;
// Remove the wrapper from the palette.
if (
draggedItemId != "separator" &&
draggedItemId != "spring" &&
draggedItemId != "spacer"
) {
gPaletteBox.removeChild(draggedPaletteWrapper);
}
}
gCurrentDragOverItem = null;
toolboxChanged();
}
function onPaletteDragOver(aEvent) {
if (isUnwantedDragEvent(aEvent)) {
return;
}
var documentId = gToolboxDocument.documentElement.id;
if (
aEvent.dataTransfer.types.includes(
"text/toolbarwrapper-id/" + documentId.toLowerCase()
)
) {
aEvent.preventDefault();
}
}
function onPaletteDrop(aEvent) {
if (isUnwantedDragEvent(aEvent)) {
return;
}
var documentId = gToolboxDocument.documentElement.id;
var itemId = aEvent.dataTransfer.getData(
"text/toolbarwrapper-id/" + documentId
);
var wrapper = gToolboxDocument.getElementById("wrapper-" + itemId);
if (wrapper) {
// Don't allow non-removable kids (e.g., the menubar) to move.
if (wrapper.firstElementChild.getAttribute("removable") != "true") {
return;
}
var wrapperType = wrapper.getAttribute("type");
if (
wrapperType != "separator" &&
wrapperType != "spacer" &&
wrapperType != "spring"
) {
restoreItemForToolbar(wrapper.firstElementChild, wrapper);
wrapPaletteItem(document.importNode(wrapper.firstElementChild, true));
gToolbox.palette.appendChild(wrapper.firstElementChild);
}
// The item was dragged out of the toolbar.
wrapper.remove();
}
toolboxChanged();
}
function isUnwantedDragEvent(aEvent) {
try {
if (
Services.prefs.getBoolPref("toolkit.customization.unsafe_drag_events")
) {
return false;
}
} catch (ex) {}
/* Discard drag events that originated from a separate window to
prevent content->chrome privilege escalations. */
let mozSourceNode = aEvent.dataTransfer.mozSourceNode;
// mozSourceNode is null in the dragStart event handler or if
// the drag event originated in an external application.
if (!mozSourceNode) {
return true;
}
let sourceWindow = mozSourceNode.ownerGlobal;
return sourceWindow != window && sourceWindow != gToolboxDocument.defaultView;
}