michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; michael@0: michael@0: this.EXPORTED_SYMBOLS = ["CustomizableWidgets"]; michael@0: michael@0: Cu.import("resource:///modules/CustomizableUI.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", michael@0: "resource://gre/modules/PlacesUtils.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "PlacesUIUtils", michael@0: "resource:///modules/PlacesUIUtils.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "RecentlyClosedTabsAndWindowsMenuUtils", michael@0: "resource:///modules/sessionstore/RecentlyClosedTabsAndWindowsMenuUtils.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils", michael@0: "resource://gre/modules/ShortcutUtils.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu", michael@0: "resource://gre/modules/CharsetMenu.jsm"); michael@0: XPCOMUtils.defineLazyServiceGetter(this, "CharsetManager", michael@0: "@mozilla.org/charset-converter-manager;1", michael@0: "nsICharsetConverterManager"); michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, "CharsetBundle", function() { michael@0: const kCharsetBundle = "chrome://global/locale/charsetMenu.properties"; michael@0: return Services.strings.createBundle(kCharsetBundle); michael@0: }); michael@0: XPCOMUtils.defineLazyGetter(this, "BrandBundle", function() { michael@0: const kBrandBundle = "chrome://branding/locale/brand.properties"; michael@0: return Services.strings.createBundle(kBrandBundle); michael@0: }); michael@0: michael@0: const kNSXUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; michael@0: const kPrefCustomizationDebug = "browser.uiCustomization.debug"; michael@0: const kWidePanelItemClass = "panel-wide-item"; michael@0: michael@0: let gModuleName = "[CustomizableWidgets]"; michael@0: #include logging.js michael@0: michael@0: function setAttributes(aNode, aAttrs) { michael@0: let doc = aNode.ownerDocument; michael@0: for (let [name, value] of Iterator(aAttrs)) { michael@0: if (!value) { michael@0: if (aNode.hasAttribute(name)) michael@0: aNode.removeAttribute(name); michael@0: } else { michael@0: if (name == "shortcutId") { michael@0: continue; michael@0: } michael@0: if (name == "label" || name == "tooltiptext") { michael@0: let stringId = (typeof value == "string") ? value : name; michael@0: let additionalArgs = []; michael@0: if (aAttrs.shortcutId) { michael@0: let shortcut = doc.getElementById(aAttrs.shortcutId); michael@0: if (doc) { michael@0: additionalArgs.push(ShortcutUtils.prettifyShortcut(shortcut)); michael@0: } michael@0: } michael@0: value = CustomizableUI.getLocalizedProperty({id: aAttrs.id}, stringId, additionalArgs); michael@0: } michael@0: aNode.setAttribute(name, value); michael@0: } michael@0: } michael@0: } michael@0: michael@0: function updateCombinedWidgetStyle(aNode, aArea, aModifyCloseMenu) { michael@0: let inPanel = (aArea == CustomizableUI.AREA_PANEL); michael@0: let cls = inPanel ? "panel-combined-button" : "toolbarbutton-1 toolbarbutton-combined"; michael@0: let attrs = {class: cls}; michael@0: if (aModifyCloseMenu) { michael@0: attrs.closemenu = inPanel ? "none" : null; michael@0: } michael@0: attrs["cui-areatype"] = aArea ? CustomizableUI.getAreaType(aArea) : null; michael@0: for (let i = 0, l = aNode.childNodes.length; i < l; ++i) { michael@0: if (aNode.childNodes[i].localName == "separator") michael@0: continue; michael@0: setAttributes(aNode.childNodes[i], attrs); michael@0: } michael@0: } michael@0: michael@0: function addShortcut(aNode, aDocument, aItem) { michael@0: let shortcutId = aNode.getAttribute("key"); michael@0: if (!shortcutId) { michael@0: return; michael@0: } michael@0: let shortcut = aDocument.getElementById(shortcutId); michael@0: if (!shortcut) { michael@0: return; michael@0: } michael@0: aItem.setAttribute("shortcut", ShortcutUtils.prettifyShortcut(shortcut)); michael@0: } michael@0: michael@0: function fillSubviewFromMenuItems(aMenuItems, aSubview) { michael@0: let attrs = ["oncommand", "onclick", "label", "key", "disabled", michael@0: "command", "observes", "hidden", "class", "origin", michael@0: "image", "checked"]; michael@0: michael@0: let doc = aSubview.ownerDocument; michael@0: let fragment = doc.createDocumentFragment(); michael@0: for (let menuChild of aMenuItems) { michael@0: if (menuChild.hidden) michael@0: continue; michael@0: michael@0: let subviewItem; michael@0: if (menuChild.localName == "menuseparator") { michael@0: // Don't insert duplicate or leading separators. This can happen if there are michael@0: // menus (which we don't copy) above the separator. michael@0: if (!fragment.lastChild || fragment.lastChild.localName == "menuseparator") { michael@0: continue; michael@0: } michael@0: subviewItem = doc.createElementNS(kNSXUL, "menuseparator"); michael@0: } else if (menuChild.localName == "menuitem") { michael@0: subviewItem = doc.createElementNS(kNSXUL, "toolbarbutton"); michael@0: addShortcut(menuChild, doc, subviewItem); michael@0: michael@0: let item = menuChild; michael@0: if (!item.hasAttribute("onclick")) { michael@0: subviewItem.addEventListener("click", event => { michael@0: let newEvent = new doc.defaultView.MouseEvent(event.type, event); michael@0: item.dispatchEvent(newEvent); michael@0: }); michael@0: } michael@0: michael@0: if (!item.hasAttribute("oncommand")) { michael@0: subviewItem.addEventListener("command", event => { michael@0: let newEvent = doc.createEvent("XULCommandEvent"); michael@0: newEvent.initCommandEvent( michael@0: event.type, event.bubbles, event.cancelable, event.view, michael@0: event.detail, event.ctrlKey, event.altKey, event.shiftKey, michael@0: event.metaKey, event.sourceEvent); michael@0: item.dispatchEvent(newEvent); michael@0: }); michael@0: } michael@0: } else { michael@0: continue; michael@0: } michael@0: for (let attr of attrs) { michael@0: let attrVal = menuChild.getAttribute(attr); michael@0: if (attrVal) michael@0: subviewItem.setAttribute(attr, attrVal); michael@0: } michael@0: // We do this after so the .subviewbutton class doesn't get overriden. michael@0: if (menuChild.localName == "menuitem") { michael@0: subviewItem.classList.add("subviewbutton"); michael@0: } michael@0: fragment.appendChild(subviewItem); michael@0: } michael@0: aSubview.appendChild(fragment); michael@0: } michael@0: michael@0: function clearSubview(aSubview) { michael@0: let parent = aSubview.parentNode; michael@0: // We'll take the container out of the document before cleaning it out michael@0: // to avoid reflowing each time we remove something. michael@0: parent.removeChild(aSubview); michael@0: michael@0: while (aSubview.firstChild) { michael@0: aSubview.firstChild.remove(); michael@0: } michael@0: michael@0: parent.appendChild(aSubview); michael@0: } michael@0: michael@0: const CustomizableWidgets = [{ michael@0: id: "history-panelmenu", michael@0: type: "view", michael@0: viewId: "PanelUI-history", michael@0: shortcutId: "key_gotoHistory", michael@0: tooltiptext: "history-panelmenu.tooltiptext2", michael@0: defaultArea: CustomizableUI.AREA_PANEL, michael@0: onViewShowing: function(aEvent) { michael@0: // Populate our list of history michael@0: const kMaxResults = 15; michael@0: let doc = aEvent.detail.ownerDocument; michael@0: michael@0: let options = PlacesUtils.history.getNewQueryOptions(); michael@0: options.excludeQueries = true; michael@0: options.includeHidden = false; michael@0: options.resultType = options.RESULTS_AS_URI; michael@0: options.queryType = options.QUERY_TYPE_HISTORY; michael@0: options.sortingMode = options.SORT_BY_DATE_DESCENDING; michael@0: options.maxResults = kMaxResults; michael@0: let query = PlacesUtils.history.getNewQuery(); michael@0: michael@0: let items = doc.getElementById("PanelUI-historyItems"); michael@0: // Clear previous history items. michael@0: while (items.firstChild) { michael@0: items.removeChild(items.firstChild); michael@0: } michael@0: michael@0: PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase) michael@0: .asyncExecuteLegacyQueries([query], 1, options, { michael@0: handleResult: function (aResultSet) { michael@0: let onHistoryVisit = function (aUri, aEvent, aItem) { michael@0: doc.defaultView.openUILink(aUri, aEvent); michael@0: CustomizableUI.hidePanelForNode(aItem); michael@0: }; michael@0: let fragment = doc.createDocumentFragment(); michael@0: for (let row, i = 0; (row = aResultSet.getNextRow()); i++) { michael@0: try { michael@0: let uri = row.getResultByIndex(1); michael@0: let title = row.getResultByIndex(2); michael@0: let icon = row.getResultByIndex(6); michael@0: michael@0: let item = doc.createElementNS(kNSXUL, "toolbarbutton"); michael@0: item.setAttribute("label", title || uri); michael@0: item.setAttribute("targetURI", uri); michael@0: item.setAttribute("class", "subviewbutton"); michael@0: item.addEventListener("click", function (aEvent) { michael@0: onHistoryVisit(uri, aEvent, item); michael@0: }); michael@0: if (icon) michael@0: item.setAttribute("image", "moz-anno:favicon:" + icon); michael@0: fragment.appendChild(item); michael@0: } catch (e) { michael@0: ERROR("Error while showing history subview: " + e); michael@0: } michael@0: } michael@0: items.appendChild(fragment); michael@0: }, michael@0: handleError: function (aError) { michael@0: LOG("History view tried to show but had an error: " + aError); michael@0: }, michael@0: handleCompletion: function (aReason) { michael@0: LOG("History view is being shown!"); michael@0: }, michael@0: }); michael@0: michael@0: let recentlyClosedTabs = doc.getElementById("PanelUI-recentlyClosedTabs"); michael@0: while (recentlyClosedTabs.firstChild) { michael@0: recentlyClosedTabs.removeChild(recentlyClosedTabs.firstChild); michael@0: } michael@0: michael@0: let recentlyClosedWindows = doc.getElementById("PanelUI-recentlyClosedWindows"); michael@0: while (recentlyClosedWindows.firstChild) { michael@0: recentlyClosedWindows.removeChild(recentlyClosedWindows.firstChild); michael@0: } michael@0: michael@0: #ifdef MOZ_SERVICES_SYNC michael@0: let tabsFromOtherComputers = doc.getElementById("sync-tabs-menuitem2"); michael@0: if (PlacesUIUtils.shouldShowTabsFromOtherComputersMenuitem()) { michael@0: tabsFromOtherComputers.removeAttribute("hidden"); michael@0: } else { michael@0: tabsFromOtherComputers.setAttribute("hidden", true); michael@0: } michael@0: michael@0: if (PlacesUIUtils.shouldEnableTabsFromOtherComputersMenuitem()) { michael@0: tabsFromOtherComputers.removeAttribute("disabled"); michael@0: } else { michael@0: tabsFromOtherComputers.setAttribute("disabled", true); michael@0: } michael@0: #endif michael@0: michael@0: let utils = RecentlyClosedTabsAndWindowsMenuUtils; michael@0: let tabsFragment = utils.getTabsFragment(doc.defaultView, "toolbarbutton", true, michael@0: "menuRestoreAllTabsSubview.label"); michael@0: let separator = doc.getElementById("PanelUI-recentlyClosedTabs-separator"); michael@0: let elementCount = tabsFragment.childElementCount; michael@0: separator.hidden = !elementCount; michael@0: while (--elementCount >= 0) { michael@0: tabsFragment.children[elementCount].classList.add("subviewbutton"); michael@0: } michael@0: recentlyClosedTabs.appendChild(tabsFragment); michael@0: michael@0: let windowsFragment = utils.getWindowsFragment(doc.defaultView, "toolbarbutton", true, michael@0: "menuRestoreAllWindowsSubview.label"); michael@0: separator = doc.getElementById("PanelUI-recentlyClosedWindows-separator"); michael@0: elementCount = windowsFragment.childElementCount; michael@0: separator.hidden = !elementCount; michael@0: while (--elementCount >= 0) { michael@0: windowsFragment.children[elementCount].classList.add("subviewbutton"); michael@0: } michael@0: recentlyClosedWindows.appendChild(windowsFragment); michael@0: }, michael@0: onCreated: function(aNode) { michael@0: // Middle clicking recently closed items won't close the panel - cope: michael@0: let onRecentlyClosedClick = function(aEvent) { michael@0: if (aEvent.button == 1) { michael@0: CustomizableUI.hidePanelForNode(this); michael@0: } michael@0: }; michael@0: let doc = aNode.ownerDocument; michael@0: let recentlyClosedTabs = doc.getElementById("PanelUI-recentlyClosedTabs"); michael@0: let recentlyClosedWindows = doc.getElementById("PanelUI-recentlyClosedWindows"); michael@0: recentlyClosedTabs.addEventListener("click", onRecentlyClosedClick); michael@0: recentlyClosedWindows.addEventListener("click", onRecentlyClosedClick); michael@0: }, michael@0: onViewHiding: function(aEvent) { michael@0: LOG("History view is being hidden!"); michael@0: } michael@0: }, { michael@0: id: "privatebrowsing-button", michael@0: shortcutId: "key_privatebrowsing", michael@0: defaultArea: CustomizableUI.AREA_PANEL, michael@0: onCommand: function(e) { michael@0: if (e.target && e.target.ownerDocument && e.target.ownerDocument.defaultView) { michael@0: let win = e.target.ownerDocument.defaultView; michael@0: if (typeof win.OpenBrowserWindow == "function") { michael@0: win.OpenBrowserWindow({private: true}); michael@0: } michael@0: } michael@0: } michael@0: }, { michael@0: id: "save-page-button", michael@0: shortcutId: "key_savePage", michael@0: tooltiptext: "save-page-button.tooltiptext3", michael@0: defaultArea: CustomizableUI.AREA_PANEL, michael@0: onCommand: function(aEvent) { michael@0: let win = aEvent.target && michael@0: aEvent.target.ownerDocument && michael@0: aEvent.target.ownerDocument.defaultView; michael@0: if (win && typeof win.saveDocument == "function") { michael@0: win.saveDocument(win.content.document); michael@0: } michael@0: } michael@0: }, { michael@0: id: "find-button", michael@0: shortcutId: "key_find", michael@0: tooltiptext: "find-button.tooltiptext3", michael@0: defaultArea: CustomizableUI.AREA_PANEL, michael@0: onCommand: function(aEvent) { michael@0: let win = aEvent.target && michael@0: aEvent.target.ownerDocument && michael@0: aEvent.target.ownerDocument.defaultView; michael@0: if (win && win.gFindBar) { michael@0: win.gFindBar.onFindCommand(); michael@0: } michael@0: } michael@0: }, { michael@0: id: "open-file-button", michael@0: shortcutId: "openFileKb", michael@0: tooltiptext: "open-file-button.tooltiptext3", michael@0: defaultArea: CustomizableUI.AREA_PANEL, michael@0: onCommand: function(aEvent) { michael@0: let win = aEvent.target michael@0: && aEvent.target.ownerDocument michael@0: && aEvent.target.ownerDocument.defaultView; michael@0: if (win && typeof win.BrowserOpenFileWindow == "function") { michael@0: win.BrowserOpenFileWindow(); michael@0: } michael@0: } michael@0: }, { michael@0: id: "developer-button", michael@0: type: "view", michael@0: viewId: "PanelUI-developer", michael@0: shortcutId: "key_devToolboxMenuItem", michael@0: tooltiptext: "developer-button.tooltiptext2", michael@0: defaultArea: CustomizableUI.AREA_PANEL, michael@0: onViewShowing: function(aEvent) { michael@0: // Populate the subview with whatever menuitems are in the developer michael@0: // menu. We skip menu elements, because the menu panel has no way michael@0: // of dealing with those right now. michael@0: let doc = aEvent.target.ownerDocument; michael@0: let win = doc.defaultView; michael@0: michael@0: let menu = doc.getElementById("menuWebDeveloperPopup"); michael@0: michael@0: let itemsToDisplay = [...menu.children]; michael@0: // Hardcode the addition of the "work offline" menuitem at the bottom: michael@0: itemsToDisplay.push({localName: "menuseparator", getAttribute: () => {}}); michael@0: itemsToDisplay.push(doc.getElementById("goOfflineMenuitem")); michael@0: fillSubviewFromMenuItems(itemsToDisplay, doc.getElementById("PanelUI-developerItems")); michael@0: michael@0: }, michael@0: onViewHiding: function(aEvent) { michael@0: let doc = aEvent.target.ownerDocument; michael@0: clearSubview(doc.getElementById("PanelUI-developerItems")); michael@0: } michael@0: }, { michael@0: id: "sidebar-button", michael@0: type: "view", michael@0: viewId: "PanelUI-sidebar", michael@0: tooltiptext: "sidebar-button.tooltiptext2", michael@0: onViewShowing: function(aEvent) { michael@0: // Largely duplicated from the developer-button above with a couple minor michael@0: // alterations. michael@0: // Populate the subview with whatever menuitems are in the michael@0: // sidebar menu. We skip menu elements, because the menu panel has no way michael@0: // of dealing with those right now. michael@0: let doc = aEvent.target.ownerDocument; michael@0: let win = doc.defaultView; michael@0: let menu = doc.getElementById("viewSidebarMenu"); michael@0: michael@0: // First clear any existing menuitems then populate. Social sidebar michael@0: // options may not have been added yet, so we do that here. Add it to the michael@0: // standard menu first, then copy all sidebar options to the panel. michael@0: win.SocialSidebar.clearProviderMenus(); michael@0: let providerMenuSeps = menu.getElementsByClassName("social-provider-menu"); michael@0: if (providerMenuSeps.length > 0) michael@0: win.SocialSidebar.populateProviderMenu(providerMenuSeps[0]); michael@0: michael@0: fillSubviewFromMenuItems([...menu.children], doc.getElementById("PanelUI-sidebarItems")); michael@0: }, michael@0: onViewHiding: function(aEvent) { michael@0: let doc = aEvent.target.ownerDocument; michael@0: clearSubview(doc.getElementById("PanelUI-sidebarItems")); michael@0: } michael@0: }, { michael@0: id: "add-ons-button", michael@0: shortcutId: "key_openAddons", michael@0: tooltiptext: "add-ons-button.tooltiptext3", michael@0: defaultArea: CustomizableUI.AREA_PANEL, michael@0: onCommand: function(aEvent) { michael@0: let win = aEvent.target && michael@0: aEvent.target.ownerDocument && michael@0: aEvent.target.ownerDocument.defaultView; michael@0: if (win && typeof win.BrowserOpenAddonsMgr == "function") { michael@0: win.BrowserOpenAddonsMgr(); michael@0: } michael@0: } michael@0: }, { michael@0: id: "preferences-button", michael@0: defaultArea: CustomizableUI.AREA_PANEL, michael@0: #ifdef XP_WIN michael@0: label: "preferences-button.labelWin", michael@0: tooltiptext: "preferences-button.tooltipWin2", michael@0: #else michael@0: #ifdef XP_MACOSX michael@0: tooltiptext: "preferences-button.tooltiptext.withshortcut", michael@0: shortcutId: "key_preferencesCmdMac", michael@0: #else michael@0: tooltiptext: "preferences-button.tooltiptext2", michael@0: #endif michael@0: #endif michael@0: onCommand: function(aEvent) { michael@0: let win = aEvent.target && michael@0: aEvent.target.ownerDocument && michael@0: aEvent.target.ownerDocument.defaultView; michael@0: if (win && typeof win.openPreferences == "function") { michael@0: win.openPreferences(); michael@0: } michael@0: } michael@0: }, { michael@0: id: "zoom-controls", michael@0: type: "custom", michael@0: tooltiptext: "zoom-controls.tooltiptext2", michael@0: defaultArea: CustomizableUI.AREA_PANEL, michael@0: onBuild: function(aDocument) { michael@0: const kPanelId = "PanelUI-popup"; michael@0: let areaType = CustomizableUI.getAreaType(this.currentArea); michael@0: let inPanel = areaType == CustomizableUI.TYPE_MENU_PANEL; michael@0: let inToolbar = areaType == CustomizableUI.TYPE_TOOLBAR; michael@0: michael@0: let buttons = [{ michael@0: id: "zoom-out-button", michael@0: command: "cmd_fullZoomReduce", michael@0: label: true, michael@0: tooltiptext: "tooltiptext2", michael@0: shortcutId: "key_fullZoomReduce", michael@0: }, { michael@0: id: "zoom-reset-button", michael@0: command: "cmd_fullZoomReset", michael@0: tooltiptext: "tooltiptext2", michael@0: shortcutId: "key_fullZoomReset", michael@0: }, { michael@0: id: "zoom-in-button", michael@0: command: "cmd_fullZoomEnlarge", michael@0: label: true, michael@0: tooltiptext: "tooltiptext2", michael@0: shortcutId: "key_fullZoomEnlarge", michael@0: }]; michael@0: michael@0: let node = aDocument.createElementNS(kNSXUL, "toolbaritem"); michael@0: node.setAttribute("id", "zoom-controls"); michael@0: node.setAttribute("label", CustomizableUI.getLocalizedProperty(this, "label")); michael@0: node.setAttribute("title", CustomizableUI.getLocalizedProperty(this, "tooltiptext")); michael@0: // Set this as an attribute in addition to the property to make sure we can style correctly. michael@0: node.setAttribute("removable", "true"); michael@0: node.classList.add("chromeclass-toolbar-additional"); michael@0: node.classList.add("toolbaritem-combined-buttons"); michael@0: node.classList.add(kWidePanelItemClass); michael@0: michael@0: buttons.forEach(function(aButton, aIndex) { michael@0: if (aIndex != 0) michael@0: node.appendChild(aDocument.createElementNS(kNSXUL, "separator")); michael@0: let btnNode = aDocument.createElementNS(kNSXUL, "toolbarbutton"); michael@0: setAttributes(btnNode, aButton); michael@0: node.appendChild(btnNode); michael@0: }); michael@0: michael@0: // The middle node is the 'Reset Zoom' button. michael@0: let zoomResetButton = node.childNodes[2]; michael@0: let window = aDocument.defaultView; michael@0: function updateZoomResetButton() { michael@0: let updateDisplay = true; michael@0: // Label should always show 100% in customize mode, so don't update: michael@0: if (aDocument.documentElement.hasAttribute("customizing")) { michael@0: updateDisplay = false; michael@0: } michael@0: //XXXgijs in some tests we get called very early, and there's no docShell on the michael@0: // tabbrowser. This breaks the zoom toolkit code (see bug 897410). Don't let that happen: michael@0: let zoomFactor = 100; michael@0: try { michael@0: zoomFactor = Math.round(window.ZoomManager.zoom * 100); michael@0: } catch (e) {} michael@0: zoomResetButton.setAttribute("label", CustomizableUI.getLocalizedProperty( michael@0: buttons[1], "label", [updateDisplay ? zoomFactor : 100] michael@0: )); michael@0: }; michael@0: michael@0: // Register ourselves with the service so we know when the zoom prefs change. michael@0: Services.obs.addObserver(updateZoomResetButton, "browser-fullZoom:zoomChange", false); michael@0: Services.obs.addObserver(updateZoomResetButton, "browser-fullZoom:zoomReset", false); michael@0: Services.obs.addObserver(updateZoomResetButton, "browser-fullZoom:location-change", false); michael@0: michael@0: if (inPanel) { michael@0: let panel = aDocument.getElementById(kPanelId); michael@0: panel.addEventListener("popupshowing", updateZoomResetButton); michael@0: } else { michael@0: if (inToolbar) { michael@0: let container = window.gBrowser.tabContainer; michael@0: container.addEventListener("TabSelect", updateZoomResetButton); michael@0: } michael@0: updateZoomResetButton(); michael@0: } michael@0: updateCombinedWidgetStyle(node, this.currentArea, true); michael@0: michael@0: let listener = { michael@0: onWidgetAdded: function(aWidgetId, aArea, aPosition) { michael@0: if (aWidgetId != this.id) michael@0: return; michael@0: michael@0: updateCombinedWidgetStyle(node, aArea, true); michael@0: updateZoomResetButton(); michael@0: michael@0: let areaType = CustomizableUI.getAreaType(aArea); michael@0: if (areaType == CustomizableUI.TYPE_MENU_PANEL) { michael@0: let panel = aDocument.getElementById(kPanelId); michael@0: panel.addEventListener("popupshowing", updateZoomResetButton); michael@0: } else if (areaType == CustomizableUI.TYPE_TOOLBAR) { michael@0: let container = window.gBrowser.tabContainer; michael@0: container.addEventListener("TabSelect", updateZoomResetButton); michael@0: } michael@0: }.bind(this), michael@0: michael@0: onWidgetRemoved: function(aWidgetId, aPrevArea) { michael@0: if (aWidgetId != this.id) michael@0: return; michael@0: michael@0: let areaType = CustomizableUI.getAreaType(aPrevArea); michael@0: if (areaType == CustomizableUI.TYPE_MENU_PANEL) { michael@0: let panel = aDocument.getElementById(kPanelId); michael@0: panel.removeEventListener("popupshowing", updateZoomResetButton); michael@0: } else if (areaType == CustomizableUI.TYPE_TOOLBAR) { michael@0: let container = window.gBrowser.tabContainer; michael@0: container.removeEventListener("TabSelect", updateZoomResetButton); michael@0: } michael@0: michael@0: // When a widget is demoted to the palette ('removed'), it's visual michael@0: // style should change. michael@0: updateCombinedWidgetStyle(node, null, true); michael@0: updateZoomResetButton(); michael@0: }.bind(this), michael@0: michael@0: onWidgetReset: function(aWidgetNode) { michael@0: if (aWidgetNode != node) michael@0: return; michael@0: updateCombinedWidgetStyle(node, this.currentArea, true); michael@0: updateZoomResetButton(); michael@0: }.bind(this), michael@0: michael@0: onWidgetMoved: function(aWidgetId, aArea) { michael@0: if (aWidgetId != this.id) michael@0: return; michael@0: updateCombinedWidgetStyle(node, aArea, true); michael@0: updateZoomResetButton(); michael@0: }.bind(this), michael@0: michael@0: onWidgetInstanceRemoved: function(aWidgetId, aDoc) { michael@0: if (aWidgetId != this.id || aDoc != aDocument) michael@0: return; michael@0: michael@0: CustomizableUI.removeListener(listener); michael@0: Services.obs.removeObserver(updateZoomResetButton, "browser-fullZoom:zoomChange"); michael@0: Services.obs.removeObserver(updateZoomResetButton, "browser-fullZoom:zoomReset"); michael@0: Services.obs.removeObserver(updateZoomResetButton, "browser-fullZoom:location-change"); michael@0: let panel = aDoc.getElementById(kPanelId); michael@0: panel.removeEventListener("popupshowing", updateZoomResetButton); michael@0: let container = aDoc.defaultView.gBrowser.tabContainer; michael@0: container.removeEventListener("TabSelect", updateZoomResetButton); michael@0: }.bind(this), michael@0: michael@0: onCustomizeStart: function(aWindow) { michael@0: if (aWindow.document == aDocument) { michael@0: updateZoomResetButton(); michael@0: } michael@0: }, michael@0: michael@0: onCustomizeEnd: function(aWindow) { michael@0: if (aWindow.document == aDocument) { michael@0: updateZoomResetButton(); michael@0: } michael@0: }, michael@0: michael@0: onWidgetDrag: function(aWidgetId, aArea) { michael@0: if (aWidgetId != this.id) michael@0: return; michael@0: aArea = aArea || this.currentArea; michael@0: updateCombinedWidgetStyle(node, aArea, true); michael@0: }.bind(this) michael@0: }; michael@0: CustomizableUI.addListener(listener); michael@0: michael@0: return node; michael@0: } michael@0: }, { michael@0: id: "edit-controls", michael@0: type: "custom", michael@0: tooltiptext: "edit-controls.tooltiptext2", michael@0: defaultArea: CustomizableUI.AREA_PANEL, michael@0: onBuild: function(aDocument) { michael@0: let buttons = [{ michael@0: id: "cut-button", michael@0: command: "cmd_cut", michael@0: label: true, michael@0: tooltiptext: "tooltiptext2", michael@0: shortcutId: "key_cut", michael@0: }, { michael@0: id: "copy-button", michael@0: command: "cmd_copy", michael@0: label: true, michael@0: tooltiptext: "tooltiptext2", michael@0: shortcutId: "key_copy", michael@0: }, { michael@0: id: "paste-button", michael@0: command: "cmd_paste", michael@0: label: true, michael@0: tooltiptext: "tooltiptext2", michael@0: shortcutId: "key_paste", michael@0: }]; michael@0: michael@0: let node = aDocument.createElementNS(kNSXUL, "toolbaritem"); michael@0: node.setAttribute("id", "edit-controls"); michael@0: node.setAttribute("label", CustomizableUI.getLocalizedProperty(this, "label")); michael@0: node.setAttribute("title", CustomizableUI.getLocalizedProperty(this, "tooltiptext")); michael@0: // Set this as an attribute in addition to the property to make sure we can style correctly. michael@0: node.setAttribute("removable", "true"); michael@0: node.classList.add("chromeclass-toolbar-additional"); michael@0: node.classList.add("toolbaritem-combined-buttons"); michael@0: node.classList.add(kWidePanelItemClass); michael@0: michael@0: buttons.forEach(function(aButton, aIndex) { michael@0: if (aIndex != 0) michael@0: node.appendChild(aDocument.createElementNS(kNSXUL, "separator")); michael@0: let btnNode = aDocument.createElementNS(kNSXUL, "toolbarbutton"); michael@0: setAttributes(btnNode, aButton); michael@0: node.appendChild(btnNode); michael@0: }); michael@0: michael@0: updateCombinedWidgetStyle(node, this.currentArea); michael@0: michael@0: let listener = { michael@0: onWidgetAdded: function(aWidgetId, aArea, aPosition) { michael@0: if (aWidgetId != this.id) michael@0: return; michael@0: updateCombinedWidgetStyle(node, aArea); michael@0: }.bind(this), michael@0: michael@0: onWidgetRemoved: function(aWidgetId, aPrevArea) { michael@0: if (aWidgetId != this.id) michael@0: return; michael@0: // When a widget is demoted to the palette ('removed'), it's visual michael@0: // style should change. michael@0: updateCombinedWidgetStyle(node); michael@0: }.bind(this), michael@0: michael@0: onWidgetReset: function(aWidgetNode) { michael@0: if (aWidgetNode != node) michael@0: return; michael@0: updateCombinedWidgetStyle(node, this.currentArea); michael@0: }.bind(this), michael@0: michael@0: onWidgetMoved: function(aWidgetId, aArea) { michael@0: if (aWidgetId != this.id) michael@0: return; michael@0: updateCombinedWidgetStyle(node, aArea); michael@0: }.bind(this), michael@0: michael@0: onWidgetInstanceRemoved: function(aWidgetId, aDoc) { michael@0: if (aWidgetId != this.id || aDoc != aDocument) michael@0: return; michael@0: CustomizableUI.removeListener(listener); michael@0: }.bind(this), michael@0: michael@0: onWidgetDrag: function(aWidgetId, aArea) { michael@0: if (aWidgetId != this.id) michael@0: return; michael@0: aArea = aArea || this.currentArea; michael@0: updateCombinedWidgetStyle(node, aArea); michael@0: }.bind(this) michael@0: }; michael@0: CustomizableUI.addListener(listener); michael@0: michael@0: return node; michael@0: } michael@0: }, michael@0: { michael@0: id: "feed-button", michael@0: type: "view", michael@0: viewId: "PanelUI-feeds", michael@0: tooltiptext: "feed-button.tooltiptext2", michael@0: defaultArea: CustomizableUI.AREA_PANEL, michael@0: onClick: function(aEvent) { michael@0: let win = aEvent.target.ownerDocument.defaultView; michael@0: let feeds = win.gBrowser.selectedBrowser.feeds; michael@0: michael@0: // Here, we only care about the case where we have exactly 1 feed and the michael@0: // user clicked... michael@0: let isClick = (aEvent.button == 0 || aEvent.button == 1); michael@0: if (feeds && feeds.length == 1 && isClick) { michael@0: aEvent.preventDefault(); michael@0: aEvent.stopPropagation(); michael@0: win.FeedHandler.subscribeToFeed(feeds[0].href, aEvent); michael@0: CustomizableUI.hidePanelForNode(aEvent.target); michael@0: } michael@0: }, michael@0: onViewShowing: function(aEvent) { michael@0: let doc = aEvent.detail.ownerDocument; michael@0: let container = doc.getElementById("PanelUI-feeds"); michael@0: let gotView = doc.defaultView.FeedHandler.buildFeedList(container, true); michael@0: michael@0: // For no feeds or only a single one, don't show the panel. michael@0: if (!gotView) { michael@0: aEvent.preventDefault(); michael@0: aEvent.stopPropagation(); michael@0: return; michael@0: } michael@0: }, michael@0: onCreated: function(node) { michael@0: let win = node.ownerDocument.defaultView; michael@0: let selectedBrowser = win.gBrowser.selectedBrowser; michael@0: let feeds = selectedBrowser && selectedBrowser.feeds; michael@0: if (!feeds || !feeds.length) { michael@0: node.setAttribute("disabled", "true"); michael@0: } michael@0: } michael@0: }, { michael@0: id: "characterencoding-button", michael@0: type: "view", michael@0: viewId: "PanelUI-characterEncodingView", michael@0: tooltiptext: "characterencoding-button.tooltiptext2", michael@0: defaultArea: CustomizableUI.AREA_PANEL, michael@0: maybeDisableMenu: function(aDocument) { michael@0: let window = aDocument.defaultView; michael@0: return !(window.gBrowser && michael@0: window.gBrowser.docShell && michael@0: window.gBrowser.docShell.mayEnableCharacterEncodingMenu); michael@0: }, michael@0: populateList: function(aDocument, aContainerId, aSection) { michael@0: let containerElem = aDocument.getElementById(aContainerId); michael@0: michael@0: containerElem.addEventListener("command", this.onCommand, false); michael@0: michael@0: let list = this.charsetInfo[aSection]; michael@0: michael@0: for (let item of list) { michael@0: let elem = aDocument.createElementNS(kNSXUL, "toolbarbutton"); michael@0: elem.setAttribute("label", item.label); michael@0: elem.setAttribute("type", "checkbox"); michael@0: elem.section = aSection; michael@0: elem.value = item.value; michael@0: elem.setAttribute("class", "subviewbutton"); michael@0: containerElem.appendChild(elem); michael@0: } michael@0: }, michael@0: updateCurrentCharset: function(aDocument) { michael@0: let content = aDocument.defaultView.content; michael@0: let currentCharset = content && content.document && content.document.characterSet; michael@0: currentCharset = CharsetMenu.foldCharset(currentCharset); michael@0: michael@0: let pinnedContainer = aDocument.getElementById("PanelUI-characterEncodingView-pinned"); michael@0: let charsetContainer = aDocument.getElementById("PanelUI-characterEncodingView-charsets"); michael@0: let elements = [...(pinnedContainer.childNodes), ...(charsetContainer.childNodes)]; michael@0: michael@0: this._updateElements(elements, currentCharset); michael@0: }, michael@0: updateCurrentDetector: function(aDocument) { michael@0: let detectorContainer = aDocument.getElementById("PanelUI-characterEncodingView-autodetect"); michael@0: let currentDetector; michael@0: try { michael@0: currentDetector = Services.prefs.getComplexValue( michael@0: "intl.charset.detector", Ci.nsIPrefLocalizedString).data; michael@0: } catch (e) {} michael@0: michael@0: this._updateElements(detectorContainer.childNodes, currentDetector); michael@0: }, michael@0: _updateElements: function(aElements, aCurrentItem) { michael@0: if (!aElements.length) { michael@0: return; michael@0: } michael@0: let disabled = this.maybeDisableMenu(aElements[0].ownerDocument); michael@0: for (let elem of aElements) { michael@0: if (disabled) { michael@0: elem.setAttribute("disabled", "true"); michael@0: } else { michael@0: elem.removeAttribute("disabled"); michael@0: } michael@0: if (elem.value.toLowerCase() == aCurrentItem.toLowerCase()) { michael@0: elem.setAttribute("checked", "true"); michael@0: } else { michael@0: elem.removeAttribute("checked"); michael@0: } michael@0: } michael@0: }, michael@0: onViewShowing: function(aEvent) { michael@0: let document = aEvent.target.ownerDocument; michael@0: michael@0: let autoDetectLabelId = "PanelUI-characterEncodingView-autodetect-label"; michael@0: let autoDetectLabel = document.getElementById(autoDetectLabelId); michael@0: if (!autoDetectLabel.hasAttribute("value")) { michael@0: let label = CharsetBundle.GetStringFromName("charsetMenuAutodet"); michael@0: autoDetectLabel.setAttribute("value", label); michael@0: this.populateList(document, michael@0: "PanelUI-characterEncodingView-pinned", michael@0: "pinnedCharsets"); michael@0: this.populateList(document, michael@0: "PanelUI-characterEncodingView-charsets", michael@0: "otherCharsets"); michael@0: this.populateList(document, michael@0: "PanelUI-characterEncodingView-autodetect", michael@0: "detectors"); michael@0: } michael@0: this.updateCurrentDetector(document); michael@0: this.updateCurrentCharset(document); michael@0: }, michael@0: onCommand: function(aEvent) { michael@0: let node = aEvent.target; michael@0: if (!node.hasAttribute || !node.section) { michael@0: return; michael@0: } michael@0: michael@0: let window = node.ownerDocument.defaultView; michael@0: let section = node.section; michael@0: let value = node.value; michael@0: michael@0: // The behavior as implemented here is directly based off of the michael@0: // `MultiplexHandler()` method in browser.js. michael@0: if (section != "detectors") { michael@0: window.BrowserSetForcedCharacterSet(value); michael@0: } else { michael@0: // Set the detector pref. michael@0: try { michael@0: let str = Cc["@mozilla.org/supports-string;1"] michael@0: .createInstance(Ci.nsISupportsString); michael@0: str.data = value; michael@0: Services.prefs.setComplexValue("intl.charset.detector", Ci.nsISupportsString, str); michael@0: } catch (e) { michael@0: Cu.reportError("Failed to set the intl.charset.detector preference."); michael@0: } michael@0: // Prepare a browser page reload with a changed charset. michael@0: window.BrowserCharsetReload(); michael@0: } michael@0: }, michael@0: onCreated: function(aNode) { michael@0: const kPanelId = "PanelUI-popup"; michael@0: let document = aNode.ownerDocument; michael@0: michael@0: let updateButton = () => { michael@0: if (this.maybeDisableMenu(document)) michael@0: aNode.setAttribute("disabled", "true"); michael@0: else michael@0: aNode.removeAttribute("disabled"); michael@0: }; michael@0: michael@0: if (this.currentArea == CustomizableUI.AREA_PANEL) { michael@0: let panel = document.getElementById(kPanelId); michael@0: panel.addEventListener("popupshowing", updateButton); michael@0: } michael@0: michael@0: let listener = { michael@0: onWidgetAdded: (aWidgetId, aArea) => { michael@0: if (aWidgetId != this.id) michael@0: return; michael@0: if (aArea == CustomizableUI.AREA_PANEL) { michael@0: let panel = document.getElementById(kPanelId); michael@0: panel.addEventListener("popupshowing", updateButton); michael@0: } michael@0: }, michael@0: onWidgetRemoved: (aWidgetId, aPrevArea) => { michael@0: if (aWidgetId != this.id) michael@0: return; michael@0: aNode.removeAttribute("disabled"); michael@0: if (aPrevArea == CustomizableUI.AREA_PANEL) { michael@0: let panel = document.getElementById(kPanelId); michael@0: panel.removeEventListener("popupshowing", updateButton); michael@0: } michael@0: }, michael@0: onWidgetInstanceRemoved: (aWidgetId, aDoc) => { michael@0: if (aWidgetId != this.id || aDoc != document) michael@0: return; michael@0: michael@0: CustomizableUI.removeListener(listener); michael@0: let panel = aDoc.getElementById(kPanelId); michael@0: panel.removeEventListener("popupshowing", updateButton); michael@0: } michael@0: }; michael@0: CustomizableUI.addListener(listener); michael@0: if (!this.charsetInfo) { michael@0: this.charsetInfo = CharsetMenu.getData(); michael@0: } michael@0: } michael@0: }, { michael@0: id: "email-link-button", michael@0: tooltiptext: "email-link-button.tooltiptext3", michael@0: onCommand: function(aEvent) { michael@0: let win = aEvent.view; michael@0: win.MailIntegration.sendLinkForWindow(win.content); michael@0: } michael@0: }]; michael@0: michael@0: #ifdef XP_WIN michael@0: #ifdef MOZ_METRO michael@0: if (Services.metro && Services.metro.supported) { michael@0: let widgetArgs = {tooltiptext: "switch-to-metro-button2.tooltiptext"}; michael@0: let brandShortName = BrandBundle.GetStringFromName("brandShortName"); michael@0: let metroTooltip = CustomizableUI.getLocalizedProperty(widgetArgs, "tooltiptext", michael@0: [brandShortName]); michael@0: CustomizableWidgets.push({ michael@0: id: "switch-to-metro-button", michael@0: label: "switch-to-metro-button2.label", michael@0: tooltiptext: metroTooltip, michael@0: defaultArea: CustomizableUI.AREA_PANEL, michael@0: showInPrivateBrowsing: false, /* See bug 928068 */ michael@0: onCommand: function(aEvent) { michael@0: let win = aEvent.view; michael@0: if (win && typeof win.SwitchToMetro == "function") { michael@0: win.SwitchToMetro(); michael@0: } michael@0: } michael@0: }); michael@0: } michael@0: #endif michael@0: #endif michael@0: michael@0: #ifdef NIGHTLY_BUILD michael@0: /** michael@0: * The e10s button's purpose is to lower the barrier of entry michael@0: * for our Nightly testers to use e10s windows. We'll be removing it michael@0: * once remote tabs are enabled. This button should never ever make it michael@0: * to production. If it does, that'd be bad, and we should all feel bad. michael@0: */ michael@0: if (Services.prefs.getBoolPref("browser.tabs.remote")) { michael@0: let getCommandFunction = function(aOpenRemote) { michael@0: return function(aEvent) { michael@0: let win = aEvent.view; michael@0: if (win && typeof win.OpenBrowserWindow == "function") { michael@0: win.OpenBrowserWindow({remote: aOpenRemote}); michael@0: } michael@0: }; michael@0: } michael@0: michael@0: let openRemote = !Services.prefs.getBoolPref("browser.tabs.remote.autostart"); michael@0: // Like the XUL menuitem counterparts, we hard-code these strings in because michael@0: // this button should never roll into production. michael@0: let buttonLabel = openRemote ? "New e10s Window" michael@0: : "New Non-e10s Window"; michael@0: michael@0: CustomizableWidgets.push({ michael@0: id: "e10s-button", michael@0: label: buttonLabel, michael@0: tooltiptext: buttonLabel, michael@0: defaultArea: CustomizableUI.AREA_PANEL, michael@0: onCommand: getCommandFunction(openRemote), michael@0: }); michael@0: } michael@0: #endif